elanous 0.2.29 → 0.2.32

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 (775) 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/{cIpv5nU9AycwYWZI9H589 → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{058a98ba.7eb28d5e6dc03565.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/{6013-2f769370d8b4c2c6.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/{8332.6f0f49df772e163b.js → 1654.32de4b324981a188.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/1834-98b64172bc1efe77.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +149 -0
  17. package/apps/pwa/out/_next/static/chunks/{8795.32573a185af18c7b.js → 1913.7cc6fb6055b1ed3f.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/{419.736c0d6f426249a7.js → 2001.6c7e0fd02190886c.js} +1 -1
  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/{6767.1fcfc9b8be83975f.js → 2213.2ccbada92c33ea0f.js} +16 -16
  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/{4759.1f76713aefb87db5.js → 2871.0bca6137343b3f76.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/{6776.06ff1e3b464cc607.js → 2898.fd781bc7cf01a7b7.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{1782-59db1b5aa06df38a.js → 2940-619bfd8ef337c611.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/{8471.8d2a8c74f09c36ff.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/{bc324b7c.02a09dd93f6a399b.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/{7725-4f83d5541fbdb81b.js → 3541-ce3a5424ed5ad7f1.js} +1 -1
  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/3902.e1e71aaac850a045.js +34 -0
  39. package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{46bd8bea.2f4b4099172fcdb7.js → 409f1e57.d0649fc47691ebfe.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/4111.2bfedbb233ad206b.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/{1142.65d002789ab3af7d.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/4999.c064a4000c2a9e94.js +79 -0
  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/5304.04d24756f242a094.js +148 -0
  57. package/apps/pwa/out/_next/static/chunks/5307.60f2209e809bd627.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/{3396-97b97928c49190c9.js → 5458-16cd7a2b51b74c8c.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/{3403.7199205e65124486.js → 5525.86debf231e181813.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{9143.ff14585db1d275ff.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/{8398.73a19b39715eda04.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/{5591.a35e7a217734a8b9.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/6323.8672b4817a306928.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/{7375.050ea0d72968e49f.js → 6433.fc573af22bbc9ab7.js} +17 -17
  75. package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6517.4147878ac06d584d.js +90 -0
  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/6767.ea607cf0c20a25af.js +24 -0
  85. package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +93 -0
  86. package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/{1852.7f7b53991fcb99ef.js → 7502.ccd5b8065545edd0.js} +2 -2
  94. package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +8 -0
  95. package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{6534.1ab00f370db7b105.js → 7862.30a8d6482bb238eb.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/{285adb1a.5654e605dddf86d2.js → 7a1887c4.f36b88fba31da8fd.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/8018.a32663f04a6fc5f9.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/9002.e5658224e3dc1b6f.js +24 -0
  110. package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/{1516.dfaceff3fd2d0e0d.js → 9138.b6e80031afb1d973.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/{9907.49be0fcadaf214b5.js → 9221.7d22eaf353a2d4e7.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/{8767-2f6a634f5abeaa01.js → 9279-d28c5b7754d010ae.js} +1 -1
  116. package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/{3160-95c96f4d2a21dd26.js → 9398-6aed28f0a96d0d6f.js} +1 -1
  118. package/apps/pwa/out/_next/static/chunks/9407.e49124dedf0f5e72.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{328bb3ab-6628a1114fe3fb3b.js → 997ce8a5-2ad501927003b41b.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/{6853.cc5174a9dee59593.js → 9989.3694300e5f000b14.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a9717a67c2eecbaf.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c0f1ca371f710b06.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/chat/page-46430d45a9e04eac.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/control/page-a27b1e5a0d1b308f.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8ea49a4c6dd2c6e7.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/exec/page-0e5a41fac1c75396.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/field/page-31aaf1ed37c6df9a.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/intake/page-c69247e7134d329d.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/layout-66dc5cd3a0999abe.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/loops/page-d256f5d5d9552823.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/market/page-ce8f1907b8a6101c.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/morning/page-32866485913d6a04.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/not-found-7607cca9b84950c6.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/observatory/page-eeab86176b03dc3b.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5b93dbae3432b996.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f47e768b5b242b5a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-95306f892aa2d0ee.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/sessions/page-940fb6c217f6b470.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-8c66a3b24a745630.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/page-7b675dfd35c36fc9.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8bd0d3cf7665bcfc.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/{page-29900ed0011b2c83.js → page-c219f2ed171131be.js} +1 -1
  163. package/apps/pwa/out/_next/static/chunks/app/share/page-40f8fa21bdae955b.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/showroom/page-468f8313e4f1fbd9.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/today/page-4214b855d9233ea8.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/trace/page-936752c7aa58762e.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/vault/page-ec052ad359df1797.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/page-fad650e4793adb65.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/{425cfc4d.fb91158e3971c39f.js → b313b242.cb1265eea908db61.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/e5012ffe.f727e23c231f4342.js +122 -0
  176. package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/ebc7d829.264a0bcb6c750b50.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +1 -0
  184. package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.css +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +23 -23
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +22 -22
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +26 -23
  222. package/apps/pwa/out/live-run/index.html +1 -1
  223. package/apps/pwa/out/live-run/index.txt +23 -23
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/bin/elanous.mjs +7 -3
  279. package/graphs/context/nightly-condense.yaml +1 -0
  280. package/graphs/implement-loop.yaml +5 -0
  281. package/graphs/intake/intake-daily.yaml +8 -1
  282. package/graphs/intake/recipes.yaml +5 -0
  283. package/graphs/lecture/lecture-note.yaml +2 -2
  284. package/graphs/ops/harness-gc.yaml +1 -0
  285. package/graphs/release/recipes.yaml +3 -0
  286. package/graphs/release/release-loop.yaml +9 -5
  287. package/graphs/units/recipes.yaml +3 -0
  288. package/graphs/units/release-gate-recheck.yaml +15 -0
  289. package/package.json +2 -1
  290. package/scripts/check-native-tool-wiring.ts +3 -2
  291. package/scripts/ci-android-unit-tests.ts +16 -0
  292. package/scripts/ci-typecheck-changed.ts +63 -10
  293. package/scripts/coord-channel-watch.sh +22 -16
  294. package/scripts/coord-role-pack.ts +81 -2
  295. package/scripts/cron-run.ts +96 -3
  296. package/scripts/detect-test-interference.ts +38 -5
  297. package/scripts/gate-pr-cache.ts +65 -0
  298. package/scripts/gh-surface/capture.ts +126 -0
  299. package/scripts/gh-surface/gh +40 -0
  300. package/scripts/import-graph-scc.ts +38 -0
  301. package/scripts/install.ps1 +19 -0
  302. package/scripts/intake-social.ts +181 -0
  303. package/scripts/lecture-note/node.ts +71 -4
  304. package/scripts/lib/android-toolchain.ts +32 -0
  305. package/scripts/lib/deterministic-env.ts +62 -0
  306. package/scripts/lib/gate-test-env.ts +19 -0
  307. package/scripts/measure-goal-grounding-corpus.ts +2 -1
  308. package/scripts/measure-si-task-cpu.ts +143 -0
  309. package/scripts/ratchets/test-interference-patterns.allowlist.txt +255 -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-closure.ts +12 -8
  317. package/scripts/release-loop/gate-diff.ts +1 -1
  318. package/scripts/release-loop/gate-host-mirror.ts +66 -0
  319. package/scripts/release-loop/gate-node.ts +523 -75
  320. package/scripts/release-loop/gate-partial.ts +106 -9
  321. package/scripts/release-loop/gate-pr-cache.ts +66 -0
  322. package/scripts/release-loop/gate-prewarm-plan.ts +66 -0
  323. package/scripts/release-loop/gate-r2-verdict.ts +189 -0
  324. package/scripts/release-loop/gate-recheck-plan-node.ts +52 -0
  325. package/scripts/release-loop/gate-warm-artifacts.ts +253 -0
  326. package/scripts/release-loop/import-graph-scc-run.ts +54 -0
  327. package/scripts/release-loop/import-graph-scc.ts +215 -0
  328. package/scripts/release-loop/node-verdict.ts +2 -0
  329. package/scripts/release-loop/prepare-node.ts +102 -4
  330. package/scripts/release-loop/probe-pod-host-dirs-live.ts +113 -0
  331. package/scripts/release-loop/resume-release.ts +33 -4
  332. package/scripts/release-loop/resume.ts +13 -5
  333. package/scripts/release-loop/spawn-lane.ts +2 -19
  334. package/scripts/release-loop/tui-sim-node.ts +3 -2
  335. package/scripts/release-loop/unattended-release.ts +16 -0
  336. package/scripts/test-deterministic.ts +5 -61
  337. package/scripts/train-reach-grouping.ts +48 -0
  338. package/scripts/tsc-incremental-cache.ts +86 -0
  339. package/scripts/tsc-incremental-diagnostics.ts +76 -0
  340. package/scripts/tsc-main-safety-net.ts +2 -2
  341. package/src/acp/client.ts +4 -1
  342. package/src/acp/core-turn-bridge.ts +65 -8
  343. package/src/acp/elanous-extensions.ts +3 -0
  344. package/src/acp/server.ts +3 -0
  345. package/src/acp/tool-trace.ts +186 -11
  346. package/src/ad-pipeline/run.ts +4 -2
  347. package/src/agent/runner.ts +169 -3
  348. package/src/agent/self-ambient-cache.ts +156 -0
  349. package/src/agent/self-ambient.ts +8 -1
  350. package/src/agent/subagent-callable.ts +3 -2
  351. package/src/agent/task-notification.ts +5 -0
  352. package/src/agent-mission/review-loop.ts +6 -3
  353. package/src/agent-substrate/acp-implementer.ts +14 -2
  354. package/src/agent-substrate/pr-reviewer.ts +14 -0
  355. package/src/agent-substrate/self-review-cli.ts +30 -4
  356. package/src/ask-user-question/tool.ts +22 -0
  357. package/src/autopilot/build/llm-conflict-merge.ts +56 -10
  358. package/src/autopilot/pr-manager.ts +80 -7
  359. package/src/briefing/brief-items.ts +24 -22
  360. package/src/capture/frame-state-detect.ts +39 -0
  361. package/src/capture/tui-self-report.ts +149 -6
  362. package/src/chat/chat-cli.ts +10 -2
  363. package/src/chat/index.ts +138 -26
  364. package/src/chat/input-control-key.ts +5 -3
  365. package/src/chat/pickers/modal-runtime.ts +6 -0
  366. package/src/chat/pickers/modals.ts +8 -2
  367. package/src/chat/tool-interrupt-behavior.ts +38 -0
  368. package/src/chat/tool-render/dispatch.ts +5 -1
  369. package/src/chat/turn-typeahead.ts +75 -20
  370. package/src/cli/__fixtures__/agent-help-before/agent.txt +4 -2
  371. package/src/cli/agent-cli.ts +41 -13
  372. package/src/cli/ask-bare.ts +1 -0
  373. package/src/cli/config-audit.ts +123 -0
  374. package/src/cli/decisions-cli.ts +80 -6
  375. package/src/cli/import-cli.ts +12 -0
  376. package/src/cli/log-event-names.ts +17 -0
  377. package/src/cli/mcp-login.ts +21 -5
  378. package/src/cli/msg-cli.ts +89 -0
  379. package/src/cli/pod-cli.ts +14 -3
  380. package/src/cli/pr-cli.ts +273 -81
  381. package/src/cli/pr-land-base-read.ts +34 -0
  382. package/src/cli/pr-land-heavy-remote.ts +82 -26
  383. package/src/cli/pr-land-overlap-evidence.ts +109 -11
  384. package/src/cli/pr-land-pod-shadow-job.ts +106 -0
  385. package/src/cli/pr-land-pod-shadow.ts +16 -3
  386. package/src/cli/pr-land-refusal.ts +20 -5
  387. package/src/cli/pr-land-virtual-rebase.ts +108 -0
  388. package/src/cli/pty-takeover-cli.ts +88 -14
  389. package/src/cli/release-cli.ts +80 -14
  390. package/src/cli/seat-cli.ts +138 -3
  391. package/src/cli/self-update.ts +5 -2
  392. package/src/cli/sim-cli.ts +18 -0
  393. package/src/cli/tasks-cli.ts +97 -19
  394. package/src/context-bus/context-first.ts +13 -0
  395. package/src/context-bus/context-now.ts +13 -2
  396. package/src/context-bus/ops-events.ts +79 -0
  397. package/src/control-plane/lease-holder.ts +12 -8
  398. package/src/control-plane/ledger.ts +163 -4
  399. package/src/control-plane/server.ts +91 -11
  400. package/src/core-turn/run-core-turn.ts +2 -0
  401. package/src/core-turn/run-goal-loop.ts +243 -3
  402. package/src/core-turn/seat-turn-cap.ts +79 -0
  403. package/src/core-turn/short-question.ts +38 -6
  404. package/src/core-turn/types.ts +6 -0
  405. package/src/dashboard/acp-active-model.ts +42 -1
  406. package/src/dashboard/acp-boot.ts +3 -0
  407. package/src/dashboard/background-jobs-status.ts +66 -0
  408. package/src/dashboard/index.ts +339 -657
  409. package/src/dashboard/input/agent-wake.ts +242 -0
  410. package/src/dashboard/input/chat-main-entry-runtime.ts +6 -1
  411. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +4 -1
  412. package/src/dashboard/input/chat-main-session.ts +8 -1
  413. package/src/dashboard/input/chat-main-turn.ts +12 -3
  414. package/src/dashboard/input/compact-prepass-recovery.ts +33 -0
  415. package/src/dashboard/input/external-input-host.ts +49 -9
  416. package/src/dashboard/input/internal-turn.ts +261 -0
  417. package/src/dashboard/input/mouse-wiring.ts +108 -11
  418. package/src/dashboard/input/seat-autowake.ts +318 -0
  419. package/src/dashboard/input/turn-steer-bridge.ts +70 -0
  420. package/src/dashboard/log-click-runtime.ts +118 -2
  421. package/src/dashboard/loop-scheduler.ts +72 -0
  422. package/src/dashboard/seat-boot-preamble.ts +18 -0
  423. package/src/dashboard/skill-runtime.ts +2 -0
  424. package/src/dashboard/slash-runtime/dashboard-handlers.ts +126 -1181
  425. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -1
  426. package/src/dashboard/slash-runtime/index.ts +13 -0
  427. package/src/dashboard/turn-busy-teardown.ts +50 -0
  428. package/src/dashboard/turn-preamble.ts +116 -40
  429. package/src/dashboard/turn-session-layers.ts +4 -2
  430. package/src/dashboard/turn-start-segments.ts +222 -0
  431. package/src/decisions/decision-ledger.ts +45 -4
  432. package/src/discord-self-message.ts +4 -4
  433. package/src/display/prompt-row-owner.ts +146 -0
  434. package/src/display/resize-listener.ts +33 -2
  435. package/src/domains/release-tool.ts +18 -1
  436. package/src/field-note/transcribe.ts +17 -0
  437. package/src/fold-stack.ts +4 -2
  438. package/src/git-fs/index.ts +263 -36
  439. package/src/github/remote-call.ts +81 -0
  440. package/src/goals/registry.ts +46 -6
  441. package/src/goals/types.ts +7 -1
  442. package/src/graph-runner/runner.ts +1 -0
  443. package/src/grok/credential.ts +26 -0
  444. package/src/harness/gate-reserve.ts +87 -0
  445. package/src/harness/harness-clean.ts +2 -1
  446. package/src/harness/harness-cli-command.ts +52 -5
  447. package/src/harness/harness-gc.ts +2 -1
  448. package/src/harness/harness-incidents.ts +70 -20
  449. package/src/harness/harness-pod-dispatch.ts +87 -10
  450. package/src/harness/harness-queue.ts +68 -4
  451. package/src/harness/harness-stop.ts +4 -3
  452. package/src/harness/harness-substrate-default.ts +78 -7
  453. package/src/harness/harness-worktree-auto.ts +2 -1
  454. package/src/harness/harness-worktrees.ts +3 -2
  455. package/src/hooks/dispatch.ts +2 -1
  456. package/src/index.ts +156 -27
  457. package/src/input/external-input-queue.ts +64 -7
  458. package/src/intake-plane/slash.ts +14 -3
  459. package/src/keybindings.ts +4 -1
  460. package/src/llm/route-decision.ts +36 -0
  461. package/src/llm/tool-loop-steered-rejection.ts +41 -0
  462. package/src/llm-request-shape.ts +209 -0
  463. package/src/llm.ts +301 -283
  464. package/src/loops/consensus.ts +348 -0
  465. package/src/loops/msg-wake.ts +206 -4
  466. package/src/loops/observe.ts +19 -3
  467. package/src/loops/orchestrator/tick.ts +88 -12
  468. package/src/loops/orchestrator/traffic.ts +64 -1
  469. package/src/loops/registry.ts +30 -0
  470. package/src/market/signed-index.ts +14 -0
  471. package/src/maturity/feature-maturity.ts +1 -1
  472. package/src/mcp/mcp-oauth.ts +54 -5
  473. package/src/models/prompts.ts +28 -0
  474. package/src/msg/msg-sse-client.ts +223 -0
  475. package/src/msg/msg-store.ts +86 -2
  476. package/src/mss/logging/log-store.ts +225 -1
  477. package/src/mss/logging/render-categories.ts +5 -0
  478. package/src/nexus/DAEMON-THIN-inventory-2026-10-15.md +122 -0
  479. package/src/nexus/api/http-server.ts +54 -15
  480. package/src/nexus/api/loop-resources.ts +19 -3
  481. package/src/nexus/api/ops-api.ts +11 -0
  482. package/src/nexus/daemon-thin-inventory.md +37 -0
  483. package/src/nexus/index.ts +70 -0
  484. package/src/nexus/state/state.ts +1 -0
  485. package/src/oauth/store.ts +32 -7
  486. package/src/onboarding/first-run.ts +28 -3
  487. package/src/ops/overview-cache.ts +16 -4
  488. package/src/ops/overview.ts +59 -2
  489. package/src/ops/resources.ts +82 -0
  490. package/src/plugins/maker/connector-maker.ts +41 -0
  491. package/src/plugins/maker/skill-maker.ts +42 -0
  492. package/src/pr-tool/census.ts +303 -0
  493. package/src/pr-tool/inflight-registry.ts +143 -0
  494. package/src/prompt-library/chat-conciseness.ts +13 -0
  495. package/src/prompt-library/universal-preamble.ts +10 -5
  496. package/src/pty-shell/pty-manifest.ts +12 -3
  497. package/src/pty-shell/tui-run-state.ts +34 -3
  498. package/src/release-loop/gate-host-reservation.ts +97 -0
  499. package/src/release-loop/landing-freeze.ts +5 -0
  500. package/src/release-loop/light-rc.ts +5 -2
  501. package/src/release-loop/placement.ts +92 -1
  502. package/src/release-loop/rehearsal-shard-plan.ts +149 -0
  503. package/src/release-loop/rehearsal.ts +73 -17
  504. package/src/repl/index.ts +20 -2
  505. package/src/seat-loop/seat-knowhow.ts +282 -0
  506. package/src/seat-loop/seat-loop.ts +41 -2
  507. package/src/seat-loop/traffic-signal.ts +5 -1
  508. package/src/self-dev/ask-launch-flow.ts +37 -3
  509. package/src/self-dev/decompose-compose-graph.ts +28 -27
  510. package/src/self-dev/decompose-proposal.ts +53 -38
  511. package/src/self-dev/decompose.ts +17 -0
  512. package/src/self-dev/dev-pipeline.ts +31 -2
  513. package/src/self-dev/draft-residue.ts +267 -33
  514. package/src/self-dev/draft-sweep.ts +59 -4
  515. package/src/self-dev/external-call-zero.ts +77 -0
  516. package/src/self-dev/launch-preflight-text.ts +6 -0
  517. package/src/self-dev/launch-preflight.ts +25 -3
  518. package/src/self-dev/local-supervisor-judge.ts +185 -0
  519. package/src/self-dev/run-supervisor.ts +229 -19
  520. package/src/self-dev/run-types.ts +20 -0
  521. package/src/self-dev/shard-aggregate.ts +13 -2
  522. package/src/self-dev/shard-plan.ts +56 -1
  523. package/src/self-dev/shard-run.ts +207 -12
  524. package/src/self-dev/spawn-lane-files.ts +19 -0
  525. package/src/self-implement/app-api-budget.ts +51 -11
  526. package/src/self-implement/config-gate.ts +2 -1
  527. package/src/self-implement/elanous-tui-spawn.ts +14 -0
  528. package/src/self-implement/expose-gate.ts +2 -1
  529. package/src/self-implement/follow-up-placement.ts +79 -0
  530. package/src/self-implement/gate-artifacts.ts +416 -0
  531. package/src/self-implement/gate-baseline.ts +96 -5
  532. package/src/self-implement/gate-cli.ts +207 -17
  533. package/src/self-implement/gate-remote.ts +136 -26
  534. package/src/self-implement/gate-scenario.ts +85 -0
  535. package/src/self-implement/goal-author.ts +160 -8
  536. package/src/self-implement/graph-authority.ts +38 -6
  537. package/src/self-implement/graph-overlay-condition.ts +92 -1
  538. package/src/self-implement/heal-triage.ts +79 -0
  539. package/src/self-implement/host-regate.ts +97 -7
  540. package/src/self-implement/orchestrator.ts +115 -134
  541. package/src/self-implement/pr-title.ts +123 -0
  542. package/src/self-implement/run-checkpoint.ts +235 -0
  543. package/src/self-implement/run-ledger.ts +162 -15
  544. package/src/self-implement/run-stop.ts +4 -3
  545. package/src/self-implement/run-ttl.ts +28 -4
  546. package/src/self-implement/seams.ts +2 -0
  547. package/src/self-implement/shadow-stage.ts +7 -6
  548. package/src/self-implement/stop-autoheal.ts +2 -1
  549. package/src/self-implement/train-gate-remote.ts +37 -8
  550. package/src/self-implement/train-reach-grouping.ts +157 -0
  551. package/src/self-implement/train-scenario-layers.ts +133 -0
  552. package/src/self-implement/uncommitted-work.ts +2 -0
  553. package/src/session/pending-input.ts +19 -0
  554. package/src/session/session-start.ts +224 -0
  555. package/src/session-runtime/index.ts +22 -267
  556. package/src/session-runtime/parallel-dispatch.ts +22 -5
  557. package/src/shell-runner/background-surface.ts +15 -5
  558. package/src/skills/tools/agent.ts +39 -6
  559. package/src/skills/tools/dev-harness.ts +6 -4
  560. package/src/skills/tools/list-dir.ts +11 -0
  561. package/src/skills/tools/schedule-wakeup.ts +408 -0
  562. package/src/skills/tools/shell.ts +246 -4
  563. package/src/status/bar.ts +26 -0
  564. package/src/steward/digest-delivery.ts +118 -0
  565. package/src/steward/hold-hygiene.ts +120 -0
  566. package/src/steward/resync-tick.ts +114 -23
  567. package/src/steward/ta-repair-event-collect.ts +158 -0
  568. package/src/steward/triage.ts +10 -0
  569. package/src/surface/shell-runner-boot.ts +5 -2
  570. package/src/task-agent/card-evidence.ts +25 -1
  571. package/src/task-agent/green-sweep.ts +8 -3
  572. package/src/task-agent/judge.ts +76 -0
  573. package/src/task-agent/land-queue.ts +79 -0
  574. package/src/task-agent/live-moves.ts +30 -9
  575. package/src/task-agent/postland-card.ts +5 -0
  576. package/src/task-agent/prelaunch-already-done.ts +98 -0
  577. package/src/task-agent/real-launch-guard.ts +37 -0
  578. package/src/task-agent/rejudge-on-head.ts +89 -12
  579. package/src/task-agent/remote-rejudge-bind.ts +28 -8
  580. package/src/task-agent/repair-cycle.ts +45 -7
  581. package/src/task-agent/repair-worker.ts +7 -0
  582. package/src/task-agent/run-identity.ts +200 -0
  583. package/src/task-agent/shadow.ts +50 -3
  584. package/src/task-agent/task-hand.ts +182 -5
  585. package/src/task-agent/train-landing.ts +77 -0
  586. package/src/task-agent/work-order.ts +104 -0
  587. package/src/task-orchestrator/surfaces/pod-command-job.ts +151 -37
  588. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +88 -19
  589. package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +2 -1
  590. package/src/task-orchestrator/surfaces/pod-image-source.ts +2 -1
  591. package/src/task-orchestrator/surfaces/pod-install-slots.ts +121 -9
  592. package/src/task-orchestrator/surfaces/pod-lease.ts +118 -6
  593. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
  594. package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +169 -0
  595. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +27 -0
  596. package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
  597. package/src/task-orchestrator/surfaces/pod-result-marker-wait.ts +33 -0
  598. package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +67 -0
  599. package/src/task-orchestrator/surfaces/pod-source-receive.ts +19 -3
  600. package/src/task-orchestrator/surfaces/pod-warm-switch.ts +141 -0
  601. package/src/task-orchestrator/surfaces/pod-warm-ta-shadow.ts +85 -0
  602. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
  603. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +153 -0
  604. package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
  605. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +362 -0
  606. package/src/task-orchestrator/surfaces/self-implement-pod.ts +138 -19
  607. package/src/telegram.ts +2 -4
  608. package/src/tui/chat/execution-badge.ts +5 -1
  609. package/src/tui/input/pickers.ts +732 -0
  610. package/src/tui/input-lag-probe.ts +230 -0
  611. package/src/tui/slash/mission.ts +492 -0
  612. package/src/tui/slash/term.ts +877 -0
  613. package/src/tui-client/dashboard-session.ts +9 -0
  614. package/src/tui.ts +52 -0
  615. package/src/user-config/gemini-server-tools.ts +9 -0
  616. package/src/user-config.ts +53 -16
  617. package/src/util/spawn-sync-output.ts +7 -0
  618. package/src/ux-sim/fake-bin.ts +154 -0
  619. package/src/ux-sim/fake-clock.ts +36 -0
  620. package/src/ux-sim/headless-tui.ts +72 -0
  621. package/src/ux-sim/in-process-cli.ts +2 -1
  622. package/src/ux-sim/scenario.ts +255 -0
  623. package/src/ux-sim/temp-git-remote.ts +82 -0
  624. package/src/version/packed-revision.json +1 -1
  625. package/src/video-pipeline/recipes/vlog.ts +25 -7
  626. package/apps/pwa/out/_next/static/chunks/1015-9cba22a5a4554797.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/1084-4e66af7376a18db7.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/1106.3a6410b7b0c70ad4.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/1127.32fc642f70423506.js +0 -8
  630. package/apps/pwa/out/_next/static/chunks/1189-d9b8e1995a831fae.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/1224.6da98c7ad0eb7dd8.js +0 -34
  632. package/apps/pwa/out/_next/static/chunks/1370.4e3e2558f111fd5d.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/1514-b7da377fdc4eb17a.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/1577.3c24c32584e10d70.js +0 -286
  635. package/apps/pwa/out/_next/static/chunks/161.130a569855c4d68c.js +0 -24
  636. package/apps/pwa/out/_next/static/chunks/1864.16da9556e78a9236.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/1b6f6b2a.aff090122be3726e.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/2341.e8a9135e5b910dd5.js +0 -36
  639. package/apps/pwa/out/_next/static/chunks/257.10afdbe3520f2fc2.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/2658.87550e692dc493f6.js +0 -64
  641. package/apps/pwa/out/_next/static/chunks/2673.85e2f02efae7a85f.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/2766-bee623b37472ed63.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/2794.6d836872b0bf43e2.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/2806.eb57ced1a528ed39.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/283.7a121c4910e6dad7.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/2870.1dc29aa5fc01f09f.js +0 -115
  647. package/apps/pwa/out/_next/static/chunks/293.fd38e77ce5d48103.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/2993-5952a1cd3fd5d441.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/32-a523427c5bd5ed00.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/3280.af38002a6424a905.js +0 -90
  651. package/apps/pwa/out/_next/static/chunks/3292-5ddffdb2f517b44f.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/3523.d3231daaf7aba993.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/3763.577de3181e1e6ea0.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/4138-cc56b0938ef72783.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/4255-8350c43d307a8875.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/4352.a4954693499c2714.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/4421-f2dcbc6fd80ec013.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/4513-51b94aeee37c9905.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/4928-c3b72f99aa588699.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/4986-f9fc0283fb325219.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/5014.671a7b9f69c2b7b1.js +0 -94
  662. package/apps/pwa/out/_next/static/chunks/5137.d398e24710b3feb6.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/5298.0bea74885cabe754.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/5315.74c53a39739b11c4.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/5317-8b1df8d02105cd07.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/5358-33051f41a2b239af.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/5388.a2ec02501f714ad5.js +0 -62
  668. package/apps/pwa/out/_next/static/chunks/5395.e6ac948a2d15058d.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/5409-f282bf533c151849.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/5466.d0f4aba9d52acfa8.js +0 -27
  671. package/apps/pwa/out/_next/static/chunks/5564.3d28b353470fe305.js +0 -104
  672. package/apps/pwa/out/_next/static/chunks/569.b0cbfb4dee4a167a.js +0 -93
  673. package/apps/pwa/out/_next/static/chunks/5835-72465b73a8bb8d74.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/5941.045d6093139e1f7a.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/6228.47cabb83564dfe54.js +0 -24
  676. package/apps/pwa/out/_next/static/chunks/6457-8d1993002a87d0fe.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/6513.ef54cfae52351982.js +0 -2
  678. package/apps/pwa/out/_next/static/chunks/6582.13d245bf87df69f1.js +0 -148
  679. package/apps/pwa/out/_next/static/chunks/6655.c33f6acc2c61bb89.js +0 -149
  680. package/apps/pwa/out/_next/static/chunks/6731-9d7195af289c8d4c.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/6733.0be05b1db9279fc5.js +0 -79
  682. package/apps/pwa/out/_next/static/chunks/6742-90937146253a8b9e.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/687-816858f77a396a4e.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/6888.72c7b8cfb02a7e61.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/6993.c17c828ac3ef3253.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/7076.9f27ef666b0cefdd.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/7113.1403c5d451804e8c.js +0 -287
  688. package/apps/pwa/out/_next/static/chunks/7169.9dd603c726c2cd7c.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/7247-e3ec21f761f86710.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/7261-a47b72f96f5199df.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/7335-5cda81ac90310077.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/7356-c2e4af090546e2d4.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/738-59518742078fda71.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/7385.a7375f10db73243a.js +0 -77
  695. package/apps/pwa/out/_next/static/chunks/7445.cf1cf2fe109bcd96.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/7505.a2057432ab6ef99a.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/7515.31f45fac1f9c70cb.js +0 -74
  698. package/apps/pwa/out/_next/static/chunks/7658-d4ce8c72a9548a91.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/8107-0b1ae46bc5bcb788.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/8132.bfea2f016ce8d992.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/8672.e299832bc7d18ee2.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/8735.f1ef798a5f260f6f.js +0 -226
  703. package/apps/pwa/out/_next/static/chunks/8774-88f67f7d8d53d299.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/8782.252e5a405d86e4ed.js +0 -32
  705. package/apps/pwa/out/_next/static/chunks/909-9ed7aa0dba804316.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/9100.22b0301256827007.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/9140-dde77575570bc71e.js +0 -1
  708. package/apps/pwa/out/_next/static/chunks/9292-a7ad9a01c7044b93.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/9517.00627dc51b02821c.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/9725.dcbb585652d8e9c5.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/9761.a3ebf5c389c24ddc.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/9972.834d2b2b7d48f1ed.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/9995-1a90486f5ae01546.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/a435ad95.577add051b4facb2.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-83372d8b5863ebf7.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d69256d7613c140a.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-352c5e9ba3f9b858.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/app/board/page-69f1d725524201a8.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/app/botlab/page-a5635eba0d73b84e.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/app/bots/page-ad9181d84285e4ea.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/app/ceo/page-86feb08080261a2f.js +0 -1
  722. package/apps/pwa/out/_next/static/chunks/app/chat/page-ffa15987e4756d03.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/app/consult/page-11a3ab44ac327cc1.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/app/control/page-6309b11c4264d24d.js +0 -1
  725. package/apps/pwa/out/_next/static/chunks/app/decisions/page-8800472079612e87.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/app/design-check/page-d387bf3f2301882a.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/app/editor/page-048f88d8c8658da6.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/app/exec/page-6c14f9739b7f6880.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/app/field/page-2834f44f64e98d74.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/app/healthz/route-83372d8b5863ebf7.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/app/inside/page-a13df6f88e824887.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/app/intake/page-8a4397248754cf2b.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/app/layout-daf0bd329360a336.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/app/live/page-f7ef1c28764cb476.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/app/live-run/page-61e74f28b9353dac.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/app/loops/page-8aaa6dedc4c10dfb.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/app/market/page-294602ba649be829.js +0 -1
  738. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-5fa5f41ed3fce323.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/app/morning/page-33bf667a5fb297bf.js +0 -1
  740. package/apps/pwa/out/_next/static/chunks/app/not-found-5ae3cbd6e5dba515.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/app/observatory/page-eb867d16ad6fdede.js +0 -1
  742. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1ab789fa68682f7e.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6fd41b5b6ff13a71.js +0 -1
  744. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fb3d5f7642c99fba.js +0 -1
  745. package/apps/pwa/out/_next/static/chunks/app/outputs/page-70f705b2b5ed42ca.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/app/page-c9c93b358a7667b5.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1563c556b81c96ed.js +0 -1
  748. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ce89c20c45b3a673.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/app/sessions/page-423ae63d786f4ee9.js +0 -1
  750. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-591a5378a4c4447c.js +0 -1
  751. package/apps/pwa/out/_next/static/chunks/app/settings/page-a250496b19a5824b.js +0 -1
  752. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c36a9277febc5911.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/app/setup/layout-83372d8b5863ebf7.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/app/share/page-a5de57edc6d7f942.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/app/showroom/page-16bdd4c9f40498df.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/app/tasks/page-75e946e6e71cc6d8.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/app/term/page-9eb56166774eb256.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/app/today/page-1c5f9ef35dbb22c5.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/trace/page-657ff9985e508498.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/vault/page-22dc72499da68fb2.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9f92737b8766009a.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/workflows/page-094ad334e40f8169.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8bcd379852ff6287.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-6b04413f280cd344.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/de70ab42-d28d47437208d458.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/f800c5e3.46dbd7f2af8c3fce.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/f8bc19ad.47579f6d684b0760.js +0 -122
  768. package/apps/pwa/out/_next/static/chunks/framework-ca9ac4609fb9fed8.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/main-app-0dd08d32d15a030f.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/main-f0399d33955c4e01.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/pages/_app-d42caca6f8ac2211.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/pages/_error-42e713c13aa41ca8.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/webpack-8fbf8f61a377aa16.js +0 -1
  774. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +0 -1
  775. /package/apps/pwa/out/_next/static/{cIpv5nU9AycwYWZI9H589 → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
package/src/llm.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  import { debug, redactSecrets, redactSecretText } from './debug/log.js';
27
27
  import { llmUsageCostFields } from './budget/llm-cost.js';
28
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';
29
30
  import { ToolCallTiming } from './chat/tool-call-timing.js';
30
31
  import {
31
32
  dispatchWithParallelSafety,
@@ -47,7 +48,8 @@ import {
47
48
  type ProviderFallbackAttempt,
48
49
  type ProviderFallbackTerminalVerdict,
49
50
  } from './session-runtime/retry-policy.js';
50
- import { fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
51
+ import { ApiHttpError, fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
52
+ import { isShapeRelevantRejection, summarizeCodexRequestShape } from './llm-request-shape.js';
51
53
  import { BedrockProvider, isBedrockCompatibleModel, makeBedrockProvider } from './llm/bedrock-provider.js';
52
54
  import { OPEN_WEIGHT_TOOL_DISCIPLINE } from './llm/open-weight-discipline.js';
53
55
  import { makeZaiCodingPlanProvider, zaiCodingPlanAvailable } from './llm/zai-coding-plan.js';
@@ -60,8 +62,6 @@ import { undoTurnRuntime } from './tool-runtime/undo-turn-runtime.js';
60
62
  import { listSnapshots } from './undo-turn/index.js';
61
63
  // Zero-dep spec module (no tool-runtime registry) — safe for this hot path.
62
64
  import { takeHydratedTools } from './skills/tools/tool-search-spec.js';
63
- import { dispatchAskUserQuestion } from './ask-user-question/index.js';
64
- import type { AskUserQuestionResult } from './ask-user-question/index.js';
65
65
  import { isPlanModeActive } from './plan-mode/session.js';
66
66
  import { mintTurnUri } from './mss/uri/builder.js';
67
67
  import type { TurnUri } from './mss/uri/brand.js';
@@ -397,6 +397,18 @@ export interface LLMOpts {
397
397
  * e.g. driving a headless coding-agent terminal (SpawnCodingAgentHeadless
398
398
  * + PtyShell Snapshot/Send loop). Absent ⇒ no extension (default). */
399
399
  budgetGrant?: { tools: readonly string[]; perCall: number; ceiling: number };
400
+ /** TUI-GOAL-LOOP-FRICTION(#46·#63) — «종결 신호» 도구 이름(예: goal loop 의 `update_goal`).
401
+ * ① 도구 예산·탐색 예산·후속 단계의 거부를 받지 않는다 — 완료 신호를 «TOOL CALL REJECTED» 로
402
+ * 막으면 모델이 둘째 답 ⊕ 사과를 쓴다(#46).
403
+ * ② 한 턴의 호출이 «종결 신호뿐»이고 그 턴(또는 이전 도구 라운드)에 답 텍스트가 있으면, 신호를
404
+ * 디스패치한 뒤 다음 모델 턴을 열지 않고 그 답으로 끝낸다 — 빈 턴 반복 → `[NO FINAL SYNTHESIS]`(#63) 차단.
405
+ * ③ 종결 신호를 받은 뒤의 빈 턴 포기에도 이미 쓴 답이 있으면 `[NO FINAL SYNTHESIS]` 를 붙이지 않는다.
406
+ * 비우면(기본) 종전 동작 그대로. */
407
+ terminalToolNames?: readonly string[];
408
+ /** TUI-H1-GOALLOOP-EMPTY-TAIL — 호출자가 이 스트림 «앞»에 사람에게 답을 이미 보였다(예: goal loop 의 앞 iteration).
409
+ * 그러면 종결 신호 뒤 빈 턴은 이 스트림에 쓴 답이 없어도 재촉·`[NO FINAL SYNTHESIS]` 없이 빈 답으로 끝낸다.
410
+ * 비우면(기본) 종전대로 — 답이 «어디에도» 없을 때의 경고는 그대로 보인다. */
411
+ terminalPriorAnswerVisible?: boolean;
400
412
  /** Prompt-caching toggle. Honored by Anthropic (attaches
401
413
  * cache_control markers on system + tools + 2nd-to-last history
402
414
  * message) and OpenAI (passes stream_options.include_usage so
@@ -416,12 +428,9 @@ export interface LLMOpts {
416
428
  * every tool-loop turn gets a stable checkpoint identity without
417
429
  * forcing existing callers to thread the brand through. */
418
430
  turnUri?: TurnUri;
419
- /** Chat session id forwarded to the AskUserQuestion bridge when the
420
- * tool-loop self-dispatches a doom-loop intervention prompt. Lets
421
- * PWA / iOS peers receive the prompt natively via the ACP
422
- * `elanous/ask/*` envelope instead of the raw `[ASK USER]` text
423
- * fallback. Absent for CLI / startup-script callers without a chat
424
- * session context. */
431
+ /** Chat session id (context compaction · provider sticky routing).
432
+ * Absent for CLI / startup-script callers without a chat session
433
+ * context. */
425
434
  sessionId?: string;
426
435
  /** Server-side built-in tools the provider should advertise to the
427
436
  * model alongside the caller-supplied `tools`. Each provider has
@@ -2186,6 +2195,31 @@ export async function* streamCodexResponsesEvents(
2186
2195
  // applies the same tool/SSE conventions
2187
2196
  const promptCache = extra.promptCache !== false;
2188
2197
  const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2198
+ // TUI-OBS-400-SHAPE(#69) — 전송 객체·문자열을 한 번 만들어 둔다: 4xx 거절 때 «실제로 보낸 것» 의 모양을 잰다.
2199
+ const wireBody: Record<string, unknown> = {
2200
+ ...body,
2201
+ stream: true,
2202
+ // store=true wave: when caller opts in, the backend persists the
2203
+ // response and the next request can thread `previous_response_id`
2204
+ // to send only the delta input items. Default still false so any
2205
+ // caller that doesn't opt in (tests building bodies directly)
2206
+ // sees the legacy wire shape.
2207
+ store: body.store === true,
2208
+ ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2209
+ ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2210
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2211
+ // Reasoning opt-in (gpt-5 family). When the caller sets
2212
+ // body.reasoning we mirror it onto the wire AND append the
2213
+ // include directive that lets multi-turn requests thread the
2214
+ // model's encrypted reasoning context. Omit both when absent
2215
+ // so non-reasoning models / opt-out users see the legacy
2216
+ // wire shape unchanged.
2217
+ ...(body.reasoning ? {
2218
+ reasoning: body.reasoning,
2219
+ include: ['reasoning.encrypted_content'],
2220
+ } : {}),
2221
+ };
2222
+ const wireJson = JSON.stringify(wireBody);
2189
2223
  const response = await fetchApiWithRetry(
2190
2224
  url,
2191
2225
  {
@@ -2203,33 +2237,14 @@ export async function* streamCodexResponsesEvents(
2203
2237
  // Codex". See getCodexUserAgent (oauth/codex.ts).
2204
2238
  'User-Agent': getCodexUserAgent(),
2205
2239
  },
2206
- body: JSON.stringify({
2207
- ...body,
2208
- stream: true,
2209
- // store=true wave: when caller opts in, the backend persists the
2210
- // response and the next request can thread `previous_response_id`
2211
- // to send only the delta input items. Default still false so any
2212
- // caller that doesn't opt in (tests building bodies directly)
2213
- // sees the legacy wire shape.
2214
- store: body.store === true,
2215
- ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2216
- ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2217
- ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2218
- // Reasoning opt-in (gpt-5 family). When the caller sets
2219
- // body.reasoning we mirror it onto the wire AND append the
2220
- // include directive that lets multi-turn requests thread the
2221
- // model's encrypted reasoning context. Omit both when absent
2222
- // so non-reasoning models / opt-out users see the legacy
2223
- // wire shape unchanged.
2224
- ...(body.reasoning ? {
2225
- reasoning: body.reasoning,
2226
- include: ['reasoning.encrypted_content'],
2227
- } : {}),
2228
- }),
2240
+ body: wireJson,
2229
2241
  signal,
2230
2242
  },
2231
2243
  retryOptsFor('codex', 'Codex API', extra.remainingFallbacks),
2232
- );
2244
+ ).catch((err: unknown) => {
2245
+ observeCodexRequestRejection(err, wireBody, wireJson);
2246
+ throw err;
2247
+ });
2233
2248
  debug.log('llm.response.status', `codex ${response.status}`, {
2234
2249
  url, status: response.status, elapsedMs: Date.now() - startedAt,
2235
2250
  });
@@ -2367,6 +2382,19 @@ export async function* streamCodexResponsesEvents(
2367
2382
  }
2368
2383
  }
2369
2384
 
2385
+ /** TUI-OBS-400-SHAPE(#69) — Codex 가 요청을 4xx(401·403·429 제외)로 거절하면 보낸 요청의 «모양» 만 남긴다.
2386
+ * Codex 400 은 `{"detail":"Bad Request"}` 뿐이라 크기·메시지 수·도구 짝 깨짐을 여기서 잰다.
2387
+ * ⛔ 본문·프롬프트·도구 인자/출력·키는 싣지 않는다(수·바이트·종류·call_id 해시 앞 8자만).
2388
+ * 관측 실패는 오류 계약을 바꾸지 않는다 — 호출부는 원래 오류를 그대로 던진다. */
2389
+ function observeCodexRequestRejection(err: unknown, wireBody: Record<string, unknown>, wireJson: string): void {
2390
+ if (!(err instanceof ApiHttpError) || !isShapeRelevantRejection(err.status)) return;
2391
+ try {
2392
+ debug.log('llm.codex', 'request-rejected-shape', summarizeCodexRequestShape(wireBody, wireJson, err.status, err.bodyText));
2393
+ } catch {
2394
+ // Observability must not interfere with the provider error contract.
2395
+ }
2396
+ }
2397
+
2370
2398
  function toolNames(raw: unknown): string[] | undefined {
2371
2399
  if (!Array.isArray(raw)) return undefined;
2372
2400
  return raw.map(item => {
@@ -4989,6 +5017,14 @@ export interface StreamWithToolsHandlers {
4989
5017
  * before the `onText('', enriched)` re-emit so presentation hosts can
4990
5018
  * keep the user answer to `answer` and send `supplement` to logs. */
4991
5019
  onFinalAnswerSupplement?(info: { answer: string; supplement: string; finalText: string }): void;
5020
+ /** 검증 보충 — final-answer supplement attachment point. Called after the
5021
+ * final answer is settled (and after any finalization-snapshot enrichment)
5022
+ * so callers can append verification-recency / loop-closure wording after
5023
+ * the final answer. Return a non-empty string to append it to the final
5024
+ * answer output (it will be joined after `finalText` and the combined
5025
+ * supplement surfaces via `onFinalAnswerSupplement`); return
5026
+ * undefined/null/empty to keep the prior behavior unchanged. */
5027
+ onAppendFinalAnswerSupplement?(req: FinalAnswerSupplementAttachmentRequest): string | null | undefined;
4992
5028
  /** Optional: notify when a tool_call is about to fire (for log line,
4993
5029
  * HUD segment, etc). */
4994
5030
  onToolCall?(call: { id: string; name: string; args: Record<string, unknown> }): void;
@@ -5774,10 +5810,13 @@ const TOOL_LOOP_PHASE_DIRECTIVES: Record<ToolLoopFollowupPhase, (autoNarrowedRea
5774
5810
  'repair-action': () =>
5775
5811
  'REPAIR ACTION REQUIRED — execution has already failed or a debugging repair step is armed. '
5776
5812
  + 'Stop gathering more files and move to Edit/Write/RunShell/Bash or a narrow code-intel followup (Read/Lsp/AstGrep/Grep content/count), '
5813
+ + 'ops ledger/delegate tools (MsgList/MsgPost/MsgAck/Checklist*/AgentOutput) still run, '
5777
5814
  + 'or answer in plain text if no code change is needed (e.g. a read-only investigation whose finding is the deliverable).',
5778
5815
  'verify-action': () =>
5779
5816
  'VERIFY ACTION REQUIRED — edits already happened. '
5780
- + '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.',
5817
+ + 'Stop gathering more files and move to RunShell/Bash/Agent or a narrow verification followup (Read/Lsp/Grep content/count), '
5818
+ + 'ops ledger/delegate tools (MsgList/MsgPost/MsgAck/Checklist*/AgentOutput) still run, '
5819
+ + 'or answer in plain text if verification is not needed.',
5781
5820
  };
5782
5821
 
5783
5822
  const TOOL_LOOP_PHASE_REJECTION_EVENTS: Record<ToolLoopFollowupPhase, string> = {
@@ -5844,44 +5883,15 @@ function buildAutoUndoRepairNotice(result: { output?: string; restoredId?: strin
5844
5883
  ].filter(Boolean).join('\n');
5845
5884
  }
5846
5885
 
5847
- // Fallback chat text used only when the AskUserQuestion bridge isn't
5848
- // reachable (no resolver, no sessionId, dispatch error). The marker
5849
- // `[ASK USER]` is preserved so `isUserInterventionRequired` downstream
5850
- // still skips force-synthesis — that gate predates the bridge route and
5851
- // other surfaces (TUI) still rely on the marker as a hard-stop signal.
5852
- function buildAutoUndoAskUserNotice(reason: DoomLoopReason, detail?: string | null): string {
5853
- const why = reason === 'plan-mode'
5854
- ? 'Auto-undo was skipped because plan mode is active.'
5855
- : reason === 'no-undo-available'
5856
- ? 'Auto-undo was skipped because no undo snapshot is available.'
5857
- : reason === 'undo-failed'
5858
- ? 'Auto-undo was attempted but the undo runtime failed.'
5859
- : 'Retry policy did not authorize auto-undo for this failure.';
5860
- return [
5861
- '[ASK USER] The same tool call kept failing — stopping to avoid wasting more turns.',
5862
- why,
5863
- detail ? `Detail: ${detail}` : '',
5864
- 'Reply with how to proceed (retry, change approach, or stop).',
5865
- ].filter(Boolean).join(' ');
5866
- }
5867
-
5868
- // ── Doom-loop → AskUserQuestion routing ─────────────────────────
5886
+ // ── Doom-loop self-heal ─────────────────────────────────────────
5869
5887
  //
5870
- // When the tool-loop trips the doom-loop gate (same tool error 3x) it
5871
- // previously emitted a raw `[ASK USER]` chat message describing what
5872
- // went wrong. With the AskUserQuestion bridge (PR #2611 PWA, #2612
5873
- // iOS, #2613 multi-option fan-out) live across PWA + iOS surfaces, we
5874
- // can self-dispatch a structured prompt instead so the user sees a
5875
- // modal with retry / different-approach / stop options rather than
5876
- // raw English instructions intended for the LLM.
5877
- //
5878
- // Falls back to the legacy `[ASK USER]` chat text when:
5879
- // - no `sessionId` plumbed (CLI / startup script callers),
5880
- // - no resolver wired (TUI-only or dispatch error), or
5881
- // - the bridge throws (peer disconnected, surface mismatch).
5882
- //
5883
- // The fallback marker `[ASK USER]` is preserved so the downstream
5884
- // `isUserInterventionRequired` gate continues to skip force-synthesis.
5888
+ // When the tool-loop trips the doom-loop gate (same tool error 3x) and
5889
+ // auto-undo cannot run, the loop heals itself instead of asking the user
5890
+ // (대표 10-17 — doom-loop is not a user-facing notice: in 40 h of runs it
5891
+ // tripped 3 times and the AskUser prompt was answered 0 times, each one
5892
+ // sitting 5 min until timeout). First trip → a «change approach» note for
5893
+ // the model and the turn continues; a second trip in the same turn → the
5894
+ // turn ends with a plain stop text (no question to the user).
5885
5895
 
5886
5896
  export type DoomLoopReason = 'plan-mode' | 'no-undo-available' | 'undo-failed' | 'policy';
5887
5897
 
@@ -5971,174 +5981,35 @@ function stringifyDoomFailureDetail(value: unknown): string {
5971
5981
  }
5972
5982
 
5973
5983
  export type DoomLoopRouteOutcome =
5974
- | { kind: 'retry'; interventionClass: DoomLoopInterventionClass }
5975
5984
  | { kind: 'guidance'; userMessage: string; interventionClass: DoomLoopInterventionClass }
5976
- | { kind: 'stop'; hardStopText: string; interventionClass: DoomLoopInterventionClass }
5977
- | { kind: 'fallback'; hardStopText: string; interventionClass: DoomLoopInterventionClass };
5978
-
5979
- export interface DoomLoopRouteOpts {
5980
- reason: DoomLoopReason;
5981
- /** Explicit evidence only. Omission means the intervention class is unknown. */
5985
+ | { kind: 'stop'; hardStopText: string; interventionClass: DoomLoopInterventionClass };
5986
+
5987
+ /** Visible text when the turn ends on a second doom trip. Deliberately not a
5988
+ * question and without the `[ASK USER]` marker — nobody is asked. */
5989
+ export const DOOM_LOOP_SELF_HEAL_STOP_TEXT =
5990
+ '같은 도구 호출이 방법을 바꾼 뒤에도 계속 실패해 이번 턴을 여기서 마칩니다.';
5991
+
5992
+ export interface DoomLoopSelfHealInput {
5993
+ /** 1 = first trip in this turn, 2+ = tripped again after the guidance. */
5994
+ attempt: number;
5995
+ tool?: string;
5996
+ fingerprint: string;
5982
5997
  interventionClass?: DoomLoopInterventionClass | null;
5983
- detail?: string | null;
5984
- doomWindow: string[];
5985
- sessionId?: string;
5986
- signal?: AbortSignal;
5987
- /** Override the AskUser gate timeout (ms). Defaults to
5988
- * DOOM_ASK_TIMEOUT_MS. Primarily a test seam. */
5989
- timeoutMs?: number;
5990
- }
5991
-
5992
- export function buildDoomLoopAskRequest(opts: DoomLoopRouteOpts): Record<string, unknown> {
5993
- const interventionClass = normalizeDoomLoopInterventionClass(opts.interventionClass);
5994
- const why = opts.reason === 'plan-mode'
5995
- ? 'plan mode is active so auto-undo was skipped'
5996
- : opts.reason === 'undo-failed'
5997
- ? `auto-undo was attempted but failed (${opts.detail ?? 'no detail'})`
5998
- : `auto-undo was not authorized by the retry policy (${opts.detail ?? 'no detail'})`;
5999
- const lastFingerprint = opts.doomWindow[opts.doomWindow.length - 1] ?? 'unknown';
6000
- return {
6001
- interventionClass,
6002
- questions: [
6003
- {
6004
- id: 'doom_loop_next_step',
6005
- header: 'Loop blocked',
6006
- question:
6007
- 'The same tool call kept failing after 3 attempts — '
6008
- + `${why}. How should I proceed?\n\nLast failure: ${lastFingerprint}`,
6009
- options: [
6010
- {
6011
- label: 'Retry once more',
6012
- description: 'Reset the retry tracker and let me try the same approach one more time.',
6013
- },
6014
- {
6015
- label: 'Different approach',
6016
- description: 'Type guidance in Other and I will follow it on the next turn.',
6017
- },
6018
- {
6019
- label: 'Stop here',
6020
- description: 'Halt this run. I will summarize what was tried and what was blocked.',
6021
- },
6022
- ],
6023
- includeOther: true,
6024
- },
6025
- ],
6026
- };
6027
5998
  }
6028
5999
 
6029
- export function interpretDoomLoopAskResult(
6030
- result: AskUserQuestionResult,
6031
- interventionClass: DoomLoopInterventionClass = 'unknown',
6032
- ): DoomLoopRouteOutcome {
6033
- if (result.cancelled === true) {
6034
- return {
6035
- kind: 'stop',
6036
- interventionClass,
6037
- hardStopText:
6038
- '[ASK USER] The same tool call kept failing and you cancelled the prompt — stopping this run.',
6039
- };
6040
- }
6041
- const answer = result.answers?.['doom_loop_next_step'];
6042
- const picked = Array.isArray(answer) ? (answer[0] ?? '') : (answer ?? '');
6043
- const otherText = result.otherText?.['doom_loop_next_step']?.trim();
6044
-
6045
- if (picked.startsWith('Retry')) {
6046
- return { kind: 'retry', interventionClass };
6047
- }
6048
- if (picked.startsWith('Different') || picked === 'Other') {
6049
- const guidance = otherText && otherText.length > 0
6050
- ? otherText
6051
- : 'Please try a different approach to the same goal — the previous tool call kept failing.';
6052
- return { kind: 'guidance', userMessage: guidance, interventionClass };
6053
- }
6054
- if (picked.startsWith('Stop')) {
6000
+ export function decideDoomLoopSelfHeal(input: DoomLoopSelfHealInput): DoomLoopRouteOutcome {
6001
+ const interventionClass = normalizeDoomLoopInterventionClass(input.interventionClass);
6002
+ if (input.attempt <= 1) {
6055
6003
  return {
6056
- kind: 'stop',
6004
+ kind: 'guidance',
6057
6005
  interventionClass,
6058
- hardStopText: '[ASK USER] Stopped at your request after the repeated tool failure.',
6059
- };
6060
- }
6061
- return {
6062
- kind: 'stop',
6063
- interventionClass,
6064
- hardStopText:
6065
- '[ASK USER] The same tool call kept failing and the response was unrecognized — stopping this run.',
6066
- };
6067
- }
6068
-
6069
- // Doom-loop AskUser gate timeout — the doom-loop HITL prompt awaits an
6070
- // operator answer with NO timeout of its own (TUI modal `await modal.promise`
6071
- // and the ACP bridge `Promise.race([pushAskRequest, cancellation])` both lack
6072
- // a timer). In an autonomous drive there is no operator to answer, so the
6073
- // dispatch region freezes indefinitely — between tool-loop turns, invisible to
6074
- // the stream idle-watchdog (armed only inside the stream loop) and to the
6075
- // goal-loop (which awaits the hung iteration). This was the 16-minute freeze in
6076
- // RESEARCH-autonomous-runaway-discipline-2026-07-19 (R2). Bound the wait: after
6077
- // this budget, fall back to the same auto-undo notice used when no session /
6078
- // no cap-able peer is present. Generous enough that a present operator answers
6079
- // first; short enough that an absent one doesn't stall the run for long.
6080
- const DOOM_ASK_TIMEOUT_MS = 5 * 60_000;
6081
-
6082
- export async function routeDoomLoopToAskUser(opts: DoomLoopRouteOpts): Promise<DoomLoopRouteOutcome> {
6083
- const interventionClass = normalizeDoomLoopInterventionClass(opts.interventionClass);
6084
- if (!opts.sessionId) {
6085
- return {
6086
- kind: 'fallback',
6087
- interventionClass,
6088
- hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
6089
- };
6090
- }
6091
- try {
6092
- const dispatchCtx: { sessionId: string; signal?: AbortSignal } = { sessionId: opts.sessionId };
6093
- if (opts.signal) dispatchCtx.signal = opts.signal;
6094
- const timeoutMs = opts.timeoutMs ?? DOOM_ASK_TIMEOUT_MS;
6095
- const TIMED_OUT = Symbol('doom-ask-timeout');
6096
- let askTimer: ReturnType<typeof setTimeout> | undefined;
6097
- const raced = await Promise.race([
6098
- dispatchAskUserQuestion(buildDoomLoopAskRequest(opts), dispatchCtx),
6099
- new Promise<typeof TIMED_OUT>((res) => {
6100
- askTimer = setTimeout(() => res(TIMED_OUT), timeoutMs);
6101
- }),
6102
- ]);
6103
- if (askTimer) clearTimeout(askTimer);
6104
- if (raced === TIMED_OUT) {
6105
- debug.log('llm.tool-loop.retry', 'doom-loop-ask-timeout', {
6106
- reason: opts.reason,
6107
- interventionClass,
6108
- timeoutMs,
6109
- }, { level: 'warn' });
6110
- return {
6111
- kind: 'fallback',
6112
- interventionClass,
6113
- hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
6114
- };
6115
- }
6116
- const r = raced;
6117
- if (!r.result) {
6118
- debug.log('llm.tool-loop.retry', 'doom-loop-ask-route-failed', {
6119
- error: r.output,
6120
- reason: opts.reason,
6121
- interventionClass,
6122
- }, { level: 'error' });
6123
- return {
6124
- kind: 'fallback',
6125
- interventionClass,
6126
- hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
6127
- };
6128
- }
6129
- return interpretDoomLoopAskResult(r.result, interventionClass);
6130
- } catch (err) {
6131
- debug.log('llm.tool-loop.retry', 'doom-loop-ask-route-failed', {
6132
- error: err instanceof Error ? err.message : String(err),
6133
- reason: opts.reason,
6134
- interventionClass,
6135
- }, { level: 'error' });
6136
- return {
6137
- kind: 'fallback',
6138
- interventionClass,
6139
- hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
6006
+ userMessage:
6007
+ `The same ${input.tool ?? 'tool'} call failed 3 times in a row (last failure: ${input.fingerprint}). `
6008
+ + 'Do not call it again with the same arguments. Change approach — a different tool or different '
6009
+ + 'arguments, or first verify the precondition that keeps failing — or state plainly what is blocked.',
6140
6010
  };
6141
6011
  }
6012
+ return { kind: 'stop', interventionClass, hardStopText: DOOM_LOOP_SELF_HEAL_STOP_TEXT };
6142
6013
  }
6143
6014
 
6144
6015
  interface AutoNarrowedInspection {
@@ -7819,6 +7690,7 @@ const EXPLORATORY_TOOLS = new Set([
7819
7690
  ]);
7820
7691
 
7821
7692
  const INSPECT_ACTION_TOOLS = new Set([
7693
+ ...ACTION_PHASE_OPS_TOOLS,
7822
7694
  'Edit',
7823
7695
  'Write',
7824
7696
  'Bash',
@@ -7833,6 +7705,7 @@ const INSPECT_ACTION_TOOLS = new Set([
7833
7705
  ]);
7834
7706
 
7835
7707
  const VERIFY_ACTION_TOOLS = new Set([
7708
+ ...ACTION_PHASE_OPS_TOOLS,
7836
7709
  'Edit',
7837
7710
  'Write',
7838
7711
  'RunShell',
@@ -7848,6 +7721,7 @@ const VERIFY_ACTION_TOOLS = new Set([
7848
7721
  ]);
7849
7722
 
7850
7723
  const EXECUTION_ACTION_TOOLS = new Set([
7724
+ ...ACTION_PHASE_OPS_TOOLS,
7851
7725
  'RunShell',
7852
7726
  'Bash',
7853
7727
  'Agent',
@@ -7861,6 +7735,8 @@ const EXECUTION_ACTION_TOOLS = new Set([
7861
7735
  ]);
7862
7736
 
7863
7737
  const REPAIR_ACTION_TOOLS = new Set([
7738
+ ...REPAIR_PHASE_READ_ONLY_OPS_TOOLS,
7739
+ ...ACTION_PHASE_OPS_TOOLS,
7864
7740
  'Edit',
7865
7741
  'Write',
7866
7742
  'RunShell',
@@ -8147,9 +8023,10 @@ export function billingRouteForProvider(provider: string | undefined, model: str
8147
8023
  // local 은 호출이 «일어났다면» 언제나 local 이다 — 주소가 env 가 아니라 config rotation 에 있어도(자격 판정은 env 만 본다).
8148
8024
  if (name === 'local') return 'local';
8149
8025
  try {
8026
+ // A recorded rotation decision is the authoritative route for that Codex call;
8027
+ // auth-kind inference alone can otherwise relabel credit spend as subscription/API.
8028
+ if (name === 'openai-codex' && codexCreditSpendReason()) return 'credit';
8150
8029
  const auth = authKindForProvider(name, model);
8151
- // CREDIT-BILLING-LABEL(10-14) — codex 회전이 크레딧 소진을 정한 호출은 구독이 아니라 크레딧이다.
8152
- if (auth === 'oauth' && name === 'openai-codex' && codexCreditSpendReason()) return 'credit';
8153
8030
  return auth === 'oauth' ? 'subscription' : auth === 'local' ? 'local' : auth === 'apikey' ? 'api' : 'unknown';
8154
8031
  } catch {
8155
8032
  return 'unknown';
@@ -8179,6 +8056,41 @@ export function logAgentTurnUsage(model: string, usage: LLMUsage, providerNameOr
8179
8056
  } catch { /* usage observation must not change the turn */ }
8180
8057
  }
8181
8058
 
8059
+ /** 검증 보충 — final-answer supplement attachment point.
8060
+ * Request passed to `StreamWithToolsHandlers.onAppendFinalAnswerSupplement`
8061
+ * after the final answer is settled: `answer` is the visible answer text and
8062
+ * `finalText` is the current final answer output (including any
8063
+ * finalization-snapshot enrichment from the onFinalAnswerSupplement
8064
+ * foundation). */
8065
+ export interface FinalAnswerSupplementAttachmentRequest {
8066
+ answer: string;
8067
+ finalText: string;
8068
+ }
8069
+
8070
+ /** Callback type for the attachment point. Return a non-empty string to have
8071
+ * it appended after the final answer; undefined/null/empty keeps the final
8072
+ * answer output unchanged. */
8073
+ export type FinalAnswerSupplementAttachment = (
8074
+ req: FinalAnswerSupplementAttachmentRequest,
8075
+ ) => string | null | undefined;
8076
+
8077
+ /** Final-answer supplement attachment point: call the optional
8078
+ * `onAppendFinalAnswerSupplement` handler and return the final answer text
8079
+ * with its supplement appended (or the original text when the handler is
8080
+ * absent or yields nothing). No numeric thresholds are applied. */
8081
+ export function attachFinalAnswerSupplement(
8082
+ handlers: { onAppendFinalAnswerSupplement?: FinalAnswerSupplementAttachment },
8083
+ req: FinalAnswerSupplementAttachmentRequest,
8084
+ ): string {
8085
+ const provider = handlers.onAppendFinalAnswerSupplement;
8086
+ if (!provider) return req.finalText;
8087
+ const supplement = provider(req);
8088
+ if (typeof supplement !== 'string' || supplement.trim().length === 0) return req.finalText;
8089
+ const base = req.finalText;
8090
+ if (base.length === 0 || base.endsWith('\n')) return base + supplement.trim();
8091
+ return base + '\n\n' + supplement.trim();
8092
+ }
8093
+
8182
8094
  export async function streamLLMWithTools(
8183
8095
  messages: LLMMessage[],
8184
8096
  handlers: StreamWithToolsHandlers,
@@ -8194,10 +8106,14 @@ export async function streamLLMWithTools(
8194
8106
  let blockedProviders: ProviderFallbackAttempt[] = [];
8195
8107
  const effectiveModel = activeModel;
8196
8108
  const modelFamily = getModelFamily(effectiveModel);
8109
+ // TUI-GPT6-GUARD-PARITY — codex «모델 행동» 가드 켬 여부(단일 원천 = models/prompts.ts
8110
+ // usesCodexBehaviorGuards). gpt-6 세대도 true. 넓힌 자리·안 넓힌 자리는 그 함수 주석이 정본.
8111
+ const codexBehaviorGuards = usesCodexBehaviorGuards(effectiveModel);
8197
8112
  debug.log('llm.router', 'streamLLMWithTools', {
8198
8113
  provider: provider.name,
8199
8114
  model: effectiveModel,
8200
8115
  modelFamily,
8116
+ codexBehaviorGuards,
8201
8117
  messageCount: messages.length,
8202
8118
  messageRoles: summarizeRoles(messages),
8203
8119
  tools: opts.tools?.map(t => t.name),
@@ -8373,6 +8289,12 @@ export async function streamLLMWithTools(
8373
8289
  };
8374
8290
  let fullText = '';
8375
8291
  let sawToolRound = false;
8292
+ // TUI-GOAL-LOOP-FRICTION — 종결 신호 도구(LLMOpts.terminalToolNames). 비면 아래 분기는 전부 꺼진다.
8293
+ const terminalToolNames: ReadonlySet<string> = new Set(opts.terminalToolNames ?? []);
8294
+ /** 이 스트림에서 종결 신호를 한 번이라도 디스패치했나. */
8295
+ let terminalSignalDispatched = false;
8296
+ /** 도구 라운드에 실린 마지막 «비지 않은» 답 텍스트(종결 신호 턴 마감·빈 턴 포기에서 쓴다). */
8297
+ let lastToolRoundText = '';
8376
8298
  const agentBatchTickIntervalMs = opts.agentBatchTickIntervalMs ?? BATCH_TICK_INTERVAL_MS;
8377
8299
 
8378
8300
  // Empty-turn guard: smaller models (gpt-5.4-mini, haiku) sometimes
@@ -8436,6 +8358,8 @@ export async function streamLLMWithTools(
8436
8358
  // instructing the model to reuse prior tool_results or write its
8437
8359
  // final answer. Codex-family-only: Claude path is unaffected.
8438
8360
  const seenToolCallSignatures = new Map<string, number>();
8361
+ // TUI-INITIATIVE-S ② — 같은 도구·같은 인자가 «같은 오류»로 끝난 횟수(성공하면 지운다). 아래 L-2 차단을 gpt 계열·비탐색 도구로 넓힌다.
8362
+ const erroredToolCallSignatures = new Map<string, { error: string; count: number }>();
8439
8363
  // W3-A + extension (2026-05-03) — Broad-spot dedup map. MUST live
8440
8364
  // outside the turn loop so the counter persists across turns. (Bug
8441
8365
  // 2026-05-03 PM: original placement inside turn loop reset the map
@@ -8490,6 +8414,8 @@ export async function streamLLMWithTools(
8490
8414
  // broken dispatch forever. Window=3 mirrors opencode's
8491
8415
  // session/processor.ts DOOM_LOOP_THRESHOLD.
8492
8416
  const toolErrorDoomTracker = new DoomLoopTracker(3);
8417
+ // Doom-loop self-heal attempts in this turn (guidance first, then stop).
8418
+ let doomSelfHealAttempts = 0;
8493
8419
  // A (RESEARCH-autonomous-runaway-discipline-2026-07-19 R1) — the error tracker
8494
8420
  // above only fires on *failing* calls; identical *successful* calls (the model
8495
8421
  // re-reading / re-grepping the same file over and over — the grep-thrash that
@@ -8993,19 +8919,37 @@ export async function streamLLMWithTools(
8993
8919
  verificationLoopState,
8994
8920
  finalization,
8995
8921
  );
8996
- if (finalText !== visibleText) {
8922
+ // 검증 보충 — final-answer supplement attachment point: let the caller
8923
+ // append verification-recency / loop-closure wording after the final
8924
+ // answer (on top of the onFinalAnswerSupplement foundation from PR #27520).
8925
+ const attachedText = attachFinalAnswerSupplement(handlers, { answer: visibleText.trim(), finalText });
8926
+ if (attachedText !== visibleText) {
8997
8927
  const answer = visibleText.trim();
8998
- handlers.onFinalAnswerSupplement?.({ answer, supplement: finalText.slice(answer.length).trim(), finalText });
8928
+ handlers.onFinalAnswerSupplement?.({ answer, supplement: attachedText.slice(answer.length).trim(), finalText: attachedText });
8999
8929
  }
9000
- if (finalText !== fullText) handlers.onText('', finalText);
9001
- emitTurnComplete(finalText);
9002
- return finalText;
8930
+ if (attachedText !== fullText) handlers.onText('', attachedText);
8931
+ emitTurnComplete(attachedText);
8932
+ return attachedText;
9003
8933
  }
9004
8934
 
9005
8935
  // No tool calls + empty text = model stalled. Re-prompt up to
9006
8936
  // MAX_EMPTY_RETRIES times. Beyond that, return an explicit
9007
8937
  // no-synthesis notice instead of silently ending the turn empty.
9008
8938
  if (pendingCalls.length === 0 && turnText.length === 0) {
8939
+ // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호(update_goal)를 이미 받았고 답도 이미 썼다 = 할 말이 없는 게 맞다.
8940
+ // 빈 턴을 «정체»로 보고 재촉·`[NO FINAL SYNTHESIS]` 를 붙이지 않고 그 답으로 끝낸다.
8941
+ // TUI-H1-GOALLOOP-EMPTY-TAIL — 답이 이 스트림 «앞»에 이미 보였어도 같다(opts.terminalPriorAnswerVisible —
8942
+ // 예: Bash → «끝» → 재주입 → update_goal 만 → 빈 턴). 이 스트림에 답이 없으면 빈 문자열로 끝내고 화면은
8943
+ // 건드리지 않는다(onText 생략 · emitTurnComplete 는 빈 답을 이력에 안 싣는다). 마감은 호출자(goal loop) 몫.
8944
+ if (terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
8945
+ const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
8946
+ debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
8947
+ turn, emptyRetries, answerChars: answer.length,
8948
+ }, { level: 'info' });
8949
+ if (answer.length > 0 && answer !== fullText) handlers.onText('', answer);
8950
+ emitTurnComplete(answer);
8951
+ return answer;
8952
+ }
9009
8953
  if (emptyRetries >= MAX_EMPTY_RETRIES) {
9010
8954
  const notice = buildNoFinalSynthesisNotice('empty-turn');
9011
8955
  const visibleText = finalizeVisibleAssistantText(
@@ -9041,6 +8985,11 @@ export async function streamLLMWithTools(
9041
8985
  clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9042
8986
  }
9043
8987
  sawToolRound = true;
8988
+ if (turnText.trim().length > 0) lastToolRoundText = turnText;
8989
+ // TUI-GOAL-LOOP-FRICTION(#46) — 이 턴의 호출이 «종결 신호뿐»이면 예산·후속 단계 거부를 걸지 않는다.
8990
+ const terminalOnlyTurn = terminalToolNames.size > 0
8991
+ && pendingCalls.length > 0
8992
+ && pendingCalls.every((call) => terminalToolNames.has(call.name));
9044
8993
  const wasExploratoryTurn = isExploratoryTurn(pendingCalls);
9045
8994
  const prevExploratoryStreak = exploratoryTurnStreak;
9046
8995
  exploratoryTurnStreak = wasExploratoryTurn ? exploratoryTurnStreak + 1 : 0;
@@ -9097,40 +9046,47 @@ export async function streamLLMWithTools(
9097
9046
  // maxTurns hard-stops it, producing 0 chars of output. Hard
9098
9047
  // rejection forces the synthesis that soft pressure couldn't.
9099
9048
  const inSynthesisPhase =
9100
- maxTurns >= BUDGET_WARNING_MIN_TURNS
9049
+ !terminalOnlyTurn
9050
+ && maxTurns >= BUDGET_WARNING_MIN_TURNS
9101
9051
  && turn >= maxTurns - FINAL_SYNTHESIS_TURNS;
9102
9052
  const codexInspectionStillProgressing =
9103
9053
  modelFamily === 'codex'
9104
9054
  && autoNarrowedReadCount > 0
9105
9055
  && autoNarrowedReadCount < inspectBudgetThreshold;
9106
9056
  const inExplorationSynthesisPhase =
9107
- exploratoryTurnStreak >= explorationThresholdForTurn
9057
+ !terminalOnlyTurn
9058
+ && exploratoryTurnStreak >= explorationThresholdForTurn
9108
9059
  && isExploratoryTurn(pendingCalls)
9109
9060
  && !codexInspectionStillProgressing;
9110
9061
  const inInspectSynthesisPhase =
9111
- inspectSynthesisArmed
9062
+ !terminalOnlyTurn
9063
+ && inspectSynthesisArmed
9112
9064
  && inspectFollowupMode === 'synthesis'
9113
9065
  && pendingCalls.length > 0;
9114
9066
  const inInspectActionPhase =
9115
- inspectSynthesisArmed
9067
+ !terminalOnlyTurn
9068
+ && inspectSynthesisArmed
9116
9069
  && inspectFollowupMode === 'action'
9117
9070
  && !verifyActionArmed
9118
9071
  && pendingCalls.length > 0
9119
9072
  && !isInspectActionTurn(pendingCalls);
9120
9073
  const inInspectExecutionPhase =
9121
- inspectSynthesisArmed
9074
+ !terminalOnlyTurn
9075
+ && inspectSynthesisArmed
9122
9076
  && inspectFollowupMode === 'execution'
9123
9077
  && !repairActionArmed
9124
9078
  && !verifyActionArmed
9125
9079
  && pendingCalls.length > 0
9126
9080
  && !isExecutionActionTurn(pendingCalls);
9127
9081
  const inRepairActionPhase =
9128
- repairActionArmed
9082
+ !terminalOnlyTurn
9083
+ && repairActionArmed
9129
9084
  && !verifyActionArmed
9130
9085
  && pendingCalls.length > 0
9131
9086
  && !isRepairActionTurn(pendingCalls);
9132
9087
  const inVerifyActionPhase =
9133
- verifyActionArmed
9088
+ !terminalOnlyTurn
9089
+ && verifyActionArmed
9134
9090
  && pendingCalls.length > 0
9135
9091
  && !isVerifyActionTurn(pendingCalls);
9136
9092
  const inInspectFollowupPhase =
@@ -9180,11 +9136,18 @@ export async function streamLLMWithTools(
9180
9136
  let callsToDispatch = pendingCalls;
9181
9137
  let appendedToolResultNotice: string | null = null;
9182
9138
  let phaseRejectionMessage: string | null = null;
9183
- const phaseRejectedCalls = getToolLoopPhaseRejectedCalls(inspectFollowupPhase, pendingCalls);
9139
+ // TUI-GOAL-LOOP-FRICTION(#46) — 섞인 턴에서도 종결 신호는 거부 대상에서 뺀다(다른 호출만 거부).
9140
+ const phaseRejectedCalls = getToolLoopPhaseRejectedCalls(inspectFollowupPhase, pendingCalls)
9141
+ .filter((call) => !terminalToolNames.has(call.name));
9184
9142
  const phaseRejectedCallIds = new Set(phaseRejectedCalls.map((call) => call.id));
9185
9143
  const rejectsWholeBatch = inSynthesisPhase || inExplorationSynthesisPhase || inInspectSynthesisPhase;
9144
+ /** 통째 거부 턴에 섞인 종결 신호 — 거부 스텁 대신 수신 확인을 준다. */
9145
+ const isTerminalCall = (call: { name: string }): boolean => terminalToolNames.has(call.name);
9146
+ const terminalAck = (call: { name: string }): string => JSON.stringify({
9147
+ ok: true, terminalSignal: call.name, note: 'terminal signal recorded; other tool calls in this turn did not run',
9148
+ });
9186
9149
  if (rejectsWholeBatch || phaseRejectedCalls.length > 0) {
9187
- const rejectedCalls = rejectsWholeBatch ? pendingCalls : phaseRejectedCalls;
9150
+ const rejectedCalls = rejectsWholeBatch ? pendingCalls.filter((call) => !isTerminalCall(call)) : phaseRejectedCalls;
9188
9151
  const executedCalls = rejectsWholeBatch ? [] : pendingCalls.filter((call) => !phaseRejectedCallIds.has(call.id));
9189
9152
  const rejectionMsg =
9190
9153
  inspectFollowupPhase
@@ -9207,8 +9170,17 @@ export async function streamLLMWithTools(
9207
9170
  phaseRejectionMessage = rejectionMsg;
9208
9171
  if (rejectsWholeBatch) {
9209
9172
  callsToDispatch = [];
9210
- for (const call of rejectedCalls) {
9211
- resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: rejectionMsg });
9173
+ // 공급자 순서를 지키려고 pendingCalls 순서대로 채운다 — 종결 신호만 수신 확인, 나머지는 거부 스텁.
9174
+ for (const call of pendingCalls) {
9175
+ if (isTerminalCall(call)) {
9176
+ terminalSignalDispatched = true;
9177
+ resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: terminalAck(call) });
9178
+ debug.log('goal.loop', 'terminal-signal-exempt-from-budget', {
9179
+ turn, maxTurns, tool: call.name, rejectedTools: rejectedCalls.map((c) => c.name),
9180
+ }, { level: 'info' });
9181
+ } else {
9182
+ resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: rejectionMsg });
9183
+ }
9212
9184
  }
9213
9185
  }
9214
9186
  // P2 (2026-05-03) — Three predicates as before, but each now consults
@@ -9459,7 +9431,9 @@ export async function streamLLMWithTools(
9459
9431
  for (const call of pendingCalls) {
9460
9432
  handlers.onToolCall?.(call);
9461
9433
  toolCallHistory.push({ name: call.name, args: call.args });
9462
- handlers.onToolResult?.({ id: call.id, name: call.name, result: phaseRejectionMessage! });
9434
+ handlers.onToolResult?.({
9435
+ id: call.id, name: call.name, result: isTerminalCall(call) ? terminalAck(call) : phaseRejectionMessage!,
9436
+ });
9463
9437
  }
9464
9438
  }
9465
9439
  if (!rejectsWholeBatch && hardStopText === null) {
@@ -9503,6 +9477,14 @@ export async function streamLLMWithTools(
9503
9477
  result: unknown,
9504
9478
  ): void => {
9505
9479
  if (call.name !== 'RunShell' && call.name !== 'Bash') return;
9480
+ if (isSteeredShellRejection(result)) {
9481
+ debug.log('llm.router', 'tool-loop.steered-rejection-not-failure', {
9482
+ turn,
9483
+ tool: call.name,
9484
+ approvalKey: (result as { approvalKey: string }).approvalKey,
9485
+ });
9486
+ return;
9487
+ }
9506
9488
  const verificationAttempt = verifyActionArmed;
9507
9489
  const executionCommand = summarizeExecutionCommand(call.name, call.args);
9508
9490
  const executionSummary = summarizeExecutionResult(result);
@@ -9954,7 +9936,14 @@ export async function streamLLMWithTools(
9954
9936
  // naturally rotates files (verified 2026-04-25T06:56
9955
9937
  // reproduction: docs/CAPABILITIES → PLAN → HANDOFF → ROADMAP
9956
9938
  // → src/code-edit/{index,apply,safety}.ts across 5 turns).
9957
- if (modelFamily === 'codex' && EXPLORATORY_TOOLS.has(call.name)) {
9939
+ // TUI-INITIATIVE-S ② — 같은 호출이 같은 오류로 DEDUP_THRESHOLD 번 끝났으면 계열·도구 무관(openai-codex·gpt 계열)하게 같은 RE-CALL 차단을 건다.
9940
+ const repeatedError = (modelFamily === 'codex' || modelFamily === 'gpt' || activeProvider.name === 'openai-codex')
9941
+ ? erroredToolCallSignatures.get(signatureOf(call)) : undefined;
9942
+ if (repeatedError && repeatedError.count >= DEDUP_THRESHOLD) {
9943
+ debug.log('llm.router', 'tool-loop.dedup-blocked', { turn, tool: call.name, signature: signatureOf(call), priorCount: repeatedError.count, threshold: DEDUP_THRESHOLD, reason: 'repeated-error' });
9944
+ 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 };
9945
+ }
9946
+ if (codexBehaviorGuards && EXPLORATORY_TOOLS.has(call.name)) {
9958
9947
  // W4-anchor — Anchor file Grep blocking. AGENTS.md / CLAUDE.md
9959
9948
  // are already in the system prompt; greppling them is pure
9960
9949
  // token waste and trains codex to ignore the in-prompt copy.
@@ -10259,6 +10248,13 @@ export async function streamLLMWithTools(
10259
10248
  // 스펙을 프로바이더에 선언하지 않는다는 의도가 우회되지 않게.
10260
10249
  const resultSignalsError = !!result && typeof result === 'object'
10261
10250
  && typeof (result as { error?: unknown }).error === 'string';
10251
+ {
10252
+ const errSig = signatureOf(call);
10253
+ const errText = resultSignalsError ? (result as { error: string }).error : isError ? String(result) : null;
10254
+ const prevErr = erroredToolCallSignatures.get(errSig);
10255
+ if (errText === null) erroredToolCallSignatures.delete(errSig);
10256
+ else erroredToolCallSignatures.set(errSig, { error: errText, count: prevErr?.error === errText ? prevErr.count + 1 : 1 });
10257
+ }
10262
10258
  if (!isError && !resultSignalsError) {
10263
10259
  for (const spec of hydrated) {
10264
10260
  if (activeToolNames.has(spec.name)) continue;
@@ -10286,7 +10282,7 @@ export async function streamLLMWithTools(
10286
10282
  // W4-A — Track Read dispatches (only successful ones; error
10287
10283
  // results don't count as "the file is in your context"). The
10288
10284
  // post-Read broad-search guard above reads this counter.
10289
- if (call.name === 'Read' && !isError && modelFamily === 'codex') {
10285
+ if (call.name === 'Read' && !isError && codexBehaviorGuards) {
10290
10286
  dispatchedReadCount++;
10291
10287
  }
10292
10288
  return { result, isError, phaseRejected: false };
@@ -10479,12 +10475,25 @@ export async function streamLLMWithTools(
10479
10475
  };
10480
10476
  const partition = phaseRejectedCalls.length > 0
10481
10477
  ? { parallelActivated: false, safeCount: 0, unsafeCount: callsToDispatch.length }
10482
- : await dispatchWithParallelSafety(callsToDispatch, dispatchWithPostProc);
10478
+ : await dispatchWithParallelSafety(callsToDispatch, dispatchWithPostProc, opts.signal);
10483
10479
  if (phaseRejectedCalls.length > 0) {
10484
10480
  for (let index = 0; index < callsToDispatch.length; index++) {
10481
+ if (opts.signal?.aborted) continue;
10485
10482
  await dispatchWithPostProc(callsToDispatch[index]!, index);
10486
10483
  }
10487
10484
  }
10485
+ // ⭐ TUI-ESC-ABORT-TOOLS — 중단(Esc)으로 «시작하지 않은» 호출은 «중단됨» 결과로 채운다
10486
+ // (tool_call/tool_result 짝 유지). `phaseRejected: true` 로 둬 doom·반복 추적에서 뺀다.
10487
+ // 다음 바퀴 머리의 `opts.signal?.aborted` 가 루프를 멈춘다.
10488
+ if (opts.signal?.aborted) {
10489
+ let skipped = 0;
10490
+ for (let index = 0; index < callsToDispatch.length; index++) {
10491
+ if (dispatchedResults[index]) continue;
10492
+ dispatchedResults[index] = { result: 'Tool call not started: the turn was aborted by the user (Esc).', isError: true, phaseRejected: true };
10493
+ skipped++;
10494
+ }
10495
+ if (skipped > 0) debug.log('llm.tools.aborted', 'batch-remaining-skipped', { turn, skipped });
10496
+ }
10488
10497
  if (debug.enabled && partition.parallelActivated) {
10489
10498
  debug.log('llm.tool-loop.parallel', 'safe-batch-dispatch', {
10490
10499
  safeCount: partition.safeCount,
@@ -10554,41 +10563,36 @@ export async function streamLLMWithTools(
10554
10563
  debug.log('llm.tool-loop.retry', 'doom-loop-detected', {
10555
10564
  turn, tool: doomToolName, window: doomWindow,
10556
10565
  }, { level: 'warn' });
10557
- const applyAskOutcome = async (reason: DoomLoopReason, detail: string | null) => {
10566
+ const applySelfHeal = (reason: DoomLoopReason, detail: string | null) => {
10558
10567
  const interventionClass = classifyDoomLoopIntervention({
10559
10568
  detail: [detail, doomFailureDetail].filter(Boolean).join('\n'), doomWindow,
10560
10569
  });
10561
- const askDispatchOpts: DoomLoopRouteOpts = {
10562
- reason, detail, doomWindow, interventionClass,
10563
- };
10564
- if (opts.sessionId) askDispatchOpts.sessionId = opts.sessionId;
10565
- if (opts.signal) askDispatchOpts.signal = opts.signal;
10566
- const outcome = await routeDoomLoopToAskUser(askDispatchOpts);
10567
- if (outcome.kind === 'retry') {
10568
- toolErrorDoomTracker.reset();
10569
- postToolResultUserNote =
10570
- 'The previous tool call kept failing 3 times in a row. Retry the same approach once more — the user reset the doom-loop tracker.';
10571
- debug.log('llm.tool-loop.retry', 'doom-loop-ask-retry', {
10572
- turn, reason, interventionClass: outcome.interventionClass,
10573
- });
10574
- } else if (outcome.kind === 'guidance') {
10570
+ doomSelfHealAttempts += 1;
10571
+ const fingerprint = doomWindow[doomWindow.length - 1] ?? 'unknown';
10572
+ const outcome = decideDoomLoopSelfHeal({
10573
+ attempt: doomSelfHealAttempts, tool: doomToolName, fingerprint, interventionClass,
10574
+ });
10575
+ debug.log('llm.tool-loop.retry', 'doom-loop-self-heal', {
10576
+ turn,
10577
+ tool: doomToolName,
10578
+ attempt: doomSelfHealAttempts,
10579
+ action: outcome.kind,
10580
+ fingerprint,
10581
+ reason,
10582
+ interventionClass: outcome.interventionClass,
10583
+ }, { level: 'warn' });
10584
+ if (outcome.kind === 'guidance') {
10575
10585
  toolErrorDoomTracker.reset();
10576
10586
  postToolResultUserNote = outcome.userMessage;
10577
- debug.log('llm.tool-loop.retry', 'doom-loop-ask-guidance', {
10578
- turn, reason, interventionClass: outcome.interventionClass,
10579
- });
10580
10587
  } else {
10581
10588
  hardStopText = outcome.hardStopText;
10582
- debug.log('llm.tool-loop.retry', outcome.kind === 'fallback' ? 'doom-loop-ask-fallback' : 'doom-loop-ask-stop', {
10583
- turn, reason, interventionClass: outcome.interventionClass, window: doomWindow,
10584
- });
10585
10589
  }
10586
10590
  };
10587
10591
  if (retryDecision.action === 'auto-undo') {
10588
10592
  if (isPlanModeActive()) {
10589
- await applyAskOutcome('plan-mode', retryDecision.reason);
10593
+ applySelfHeal('plan-mode', retryDecision.reason);
10590
10594
  } else if (listSnapshots().length === 0) {
10591
- await applyAskOutcome('no-undo-available', 'UndoTurn: no snapshots available');
10595
+ applySelfHeal('no-undo-available', 'UndoTurn: no snapshots available');
10592
10596
  } else {
10593
10597
  try {
10594
10598
  const undoResult = await undoTurnRuntime.run({}, { surface: 'tui' });
@@ -10602,11 +10606,11 @@ export async function streamLLMWithTools(
10602
10606
  window: doomWindow,
10603
10607
  });
10604
10608
  } catch (err) {
10605
- await applyAskOutcome('undo-failed', err instanceof Error ? err.message : String(err));
10609
+ applySelfHeal('undo-failed', err instanceof Error ? err.message : String(err));
10606
10610
  }
10607
10611
  }
10608
10612
  } else {
10609
- await applyAskOutcome('policy', retryDecision.reason);
10613
+ applySelfHeal('policy', retryDecision.reason);
10610
10614
  }
10611
10615
  }
10612
10616
  if (executionDoomDetectedThisTurn && !hardStopText) {
@@ -10806,10 +10810,24 @@ export async function streamLLMWithTools(
10806
10810
  if (postToolResultUserNote) {
10807
10811
  // Appended after the tool_result user turn so the wire order is
10808
10812
  // assistant(tool_use) → user(tool_result) → user(intervention).
10809
- // This is the "retry" or "guidance" branch of the doom-loop ASK
10810
- // route — see routeDoomLoopToAskUser above.
10813
+ // Also carries the doom-loop self-heal guidance — see
10814
+ // decideDoomLoopSelfHeal above.
10811
10815
  history.push({ role: LOOP_SELF_NOTE_ROLE, content: postToolResultUserNote });
10812
10816
  }
10817
+ // 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
10818
+ if (!hardStopText && pendingCalls.some(isTerminalCall)) terminalSignalDispatched = true;
10819
+ // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호뿐인 턴 ⊕ 같은 턴에 쓴 답 = 이 턴이 최종 답이다.
10820
+ // 다음 모델 턴을 열면 할 말이 없는 모델이 빈 턴을 되풀이하고 `[NO FINAL SYNTHESIS]` 가 붙는다(10-15 22:36Z 표본).
10821
+ // 이 턴에 답이 없으면 종전처럼 다음 턴을 연다(앞 라운드 글은 «하겠습니다» 류 서술일 수 있다 — 모델이 답을 쓰게).
10822
+ if (terminalOnlyTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
10823
+ const answer = turnText;
10824
+ debug.log('goal.loop', 'terminal-signal-finish', {
10825
+ turn, maxTurns, tools: pendingCalls.map((call) => call.name), answerChars: answer.length,
10826
+ }, { level: 'info' });
10827
+ if (answer !== fullText) handlers.onText('', answer);
10828
+ emitTurnComplete(answer);
10829
+ return answer;
10830
+ }
10813
10831
  if (hardStopText) {
10814
10832
  // W5-E (2026-05-03 PM) — Force-synthesis no-tools pass. Before
10815
10833
  // emitting the [SYNTHESIS IGNORED] / [ACTION IGNORED] / [VERIFY
@@ -10845,7 +10863,7 @@ export async function streamLLMWithTools(
10845
10863
  // fix shape (no-tools synthesis with prior tool_results as
10846
10864
  // context) lifts the empty reply to substantive answer.
10847
10865
  const isForceSynthesisFamily =
10848
- modelFamily === 'codex' || modelFamily === 'gemini';
10866
+ codexBehaviorGuards || modelFamily === 'gemini';
10849
10867
  const shouldForceSynthesis =
10850
10868
  isForceSynthesisFamily
10851
10869
  && !isUserInterventionRequired
@@ -11013,7 +11031,7 @@ export async function streamLLMWithTools(
11013
11031
  let synthesizedText: string | null = null;
11014
11032
  const shouldForceSynthesisOnBudget =
11015
11033
  loopTermination === 'budget-exhausted'
11016
- && (modelFamily === 'codex' || modelFamily === 'gemini')
11034
+ && (codexBehaviorGuards || modelFamily === 'gemini')
11017
11035
  && !forceSynthesisAttempted;
11018
11036
  if (shouldForceSynthesisOnBudget) {
11019
11037
  forceSynthesisAttempted = true;
@@ -11894,7 +11912,7 @@ export function getProviderForConfig(
11894
11912
  // union without a case here fails loud at first call.
11895
11913
  throw new Error(`unknown provider: ${provider as string}`);
11896
11914
  }
11897
- import { getModelFamily } from './models/prompts.js';
11915
+ import { getModelFamily, usesCodexBehaviorGuards } from './models/prompts.js';
11898
11916
  import type { ModelFamily } from './models/prompts.js';
11899
11917
 
11900
11918
  // COMPAT(llm-facade-openai-sampling-fields) · 기존 llm.ts 공개 API 유지.