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
@@ -8,6 +8,7 @@ import { join } from 'path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { createRunningBuildStatus } from './running-build-status.js';
10
10
  import { reportTuiTurn } from '../pty-shell/tui-run-state.js';
11
+ import { logTurnThrew, runTurnTeardownStep } from './turn-busy-teardown.js';
11
12
  import { createStaleInstallCheck, formatStaleInstallNotice, readCurrentInstalledCommit, readDashboardBootCommit, STALE_INSTALL_CHECK_INTERVAL_MS, versionedDashboardInstallDir } from './stale-install-check.js';
12
13
  import { spawn, spawnSync } from 'node:child_process';
13
14
  import { $ } from 'bun';
@@ -175,6 +176,7 @@ import {
175
176
  } from '../chat/turn-typeahead.js';
176
177
  import { dispatchStreamingTurnTypeaheadSubmission } from './input/streaming-turn-typeahead.js';
177
178
  import { composeVertical, type LayoutZone } from '../layout/composer.js';
179
+ import { createPromptBandRenderObserver, createPromptRowOwner, renderPromptOwnerHintRow, withPromptRowOwner } from '../display/prompt-row-owner.js';
178
180
  import type { CoreTurnDispatchTool } from '../core-turn/index.js';
179
181
  // streamGrok kept as back-compat shim in chat.ts — not imported here
180
182
  // directly anymore. Dashboard runs every turn through the in-process
@@ -200,6 +202,7 @@ import { startThinking, startPinnedThinking, type ThinkingHandle } from '../thin
200
202
  import { goalLoopIterationThinking } from './goal-loop-thinking.js';
201
203
  import { debug, getAmbientSessionId } from '../debug/log.js';
202
204
  import { startEventLoopStallMonitor, thinkingVerbFromFooter } from './event-loop-stall-monitor.js';
205
+ import { createInputLagProbe, noteInputLagPaint, setInputLagProbe } from '../tui/input-lag-probe.js';
203
206
  import { getPushcutClient } from '../pushcut/client.js';
204
207
  // P2-3 + P4 + P5 — input-core wiring. Aliased on import to dodge the
205
208
  // many existing `resolveInputEvent`-ish names elsewhere in this file.
@@ -433,6 +436,7 @@ import { collectSignals, signalsSummary } from '../tool-hints/signals.js';
433
436
  import { resetProbes } from '../tool-hints/probe.js';
434
437
  import type { HintScope } from '../tool-hints/types.js';
435
438
  import { globalAgentRegistry, type AgentToolCallEvent } from '../agent/registry.js';
439
+ import { createBackgroundJobsStatus } from './background-jobs-status.js';
436
440
  import { SELF_IMPLEMENT_TOOL_NAMES } from '../boot/daemon-tools/self-implement-names.js';
437
441
  import { type FoldMode } from '../log-entry.js';
438
442
  import { renderToolCallEvent, renderToolResultVariants } from '../chat/tool-render/index.js';
@@ -557,21 +561,50 @@ export function resumeInProcessDashboardSession(
557
561
  setAttachedSessionId: (sessionId: string) => void;
558
562
  setActiveSessionId: (sessionId: string) => void;
559
563
  },
560
- ): { resumed: true; sessionId: string; turns: number } | { resumed: false; reason: string } {
564
+ ):
565
+ | { resumed: true; sessionId: string; turns: number; repaired?: { removedCalls: number; removedResults: number } }
566
+ | { resumed: false; reason: string } {
561
567
  const resolvedId = deps.resolveSessionId(resumeSessionId);
562
568
  const loaded = resolvedId ? deps.historyFromSession(resolvedId) : null;
563
569
  if (!resolvedId || !loaded) return { resumed: false, reason: resumeSessionId };
564
570
 
571
+ // ④ 깨진 기록 방어(TUI-H8 · #69) — 도구 호출 도중 죽은 프로세스의 기록은 호출·결과 짝이 깨진 꼬리를 남겨
572
+ // 이어받은 첫 턴이 Codex 400 으로 죽는다. 고아 호출·결과만 잘라(`repairCodexToolPairs` · 짝이 맞는 것은 그대로)
573
+ // 이어받고, 같은 id 가 겹쳐 짝을 못 가리면 이어받지 않는다(새 세션 권고).
574
+ const pairs = repairCodexToolPairs(loaded.history as unknown as LLMMessage[]);
575
+ if (pairs.ambiguousIds.length > 0) {
576
+ try {
577
+ debug.log('session', 'resume-refused', {
578
+ surface: 'tui', sessionId: resolvedId, reason: 'ambiguous-tool-pairs', ambiguousIds: pairs.ambiguousIds.length,
579
+ });
580
+ } catch { /* observation is best-effort */ }
581
+ return { resumed: false, reason: `${resumeSessionId} — 도구 호출·결과 짝이 모호한 기록이다 · 새 세션으로 시작한다(권고)` };
582
+ }
583
+ const repairedCount = pairs.removedCalls + pairs.removedResults;
584
+ const history = repairedCount > 0 ? pairs.messages as unknown as ChatMessage[] : loaded.history;
585
+ if (repairedCount > 0) {
586
+ try {
587
+ debug.log('session', 'resume-history-repaired', {
588
+ surface: 'tui', sessionId: resolvedId, removedCalls: pairs.removedCalls, removedResults: pairs.removedResults,
589
+ });
590
+ } catch { /* observation is best-effort */ }
591
+ }
592
+
565
593
  const sysFromDash = deps.chatHistory.find((message) => message.role === 'system');
566
594
  deps.chatHistory.length = 0;
567
595
  if (sysFromDash) deps.chatHistory.push(sysFromDash);
568
- for (const message of loaded.history) {
596
+ for (const message of history) {
569
597
  if (message.role === 'system' && sysFromDash && message.content === sysFromDash.content) continue;
570
598
  deps.chatHistory.push(message);
571
599
  }
572
600
  deps.setAttachedSessionId(resolvedId);
573
601
  try { deps.setActiveSessionId(resolvedId); } catch { /* best-effort */ }
574
- return { resumed: true, sessionId: resolvedId, turns: loaded.history.length };
602
+ return {
603
+ resumed: true,
604
+ sessionId: resolvedId,
605
+ turns: history.length,
606
+ ...(repairedCount > 0 ? { repaired: { removedCalls: pairs.removedCalls, removedResults: pairs.removedResults } } : {}),
607
+ };
575
608
  }
576
609
 
577
610
  /**
@@ -601,7 +634,7 @@ import {
601
634
  } from '../user-config.js';
602
635
  import { streamLLM, anyProviderAvailable, getProviderForConfig, nextReasoningLevel, reasoningLevelLabel, effectiveReasoningLevel, modelSupportsReasoning, type LLMMessage, type LLMToolSpec } from '../llm.js';
603
636
  import { currentRouteDecision, formatRouteDecisionSummary } from '../llm/route-decision.js';
604
- import { resolveDashboardActiveModel } from './acp-active-model.js';
637
+ import { recordDashboardTurnExecution, resolveDashboardActiveModel } from './acp-active-model.js';
605
638
  import { getModelFamily, getModelTier } from '../models/prompts.js';
606
639
  import { buildPromptInjection, getPromptBankStore } from '../prompt-bank/index.js';
607
640
  import { getInputHistoryStore } from '../input-history.js';
@@ -635,7 +668,7 @@ import { registerDashboardPaneArtifactRuntimes } from './pane-artifact-runtime-r
635
668
  import { handleDashboardAttachmentPopupAction } from './attachment-popup-actions.js';
636
669
  import { renderDashboardAttachmentSummary } from './attachment-summary.js';
637
670
  import { openDashboardFolderAttachModal } from './folder-attach-modal-runtime.js';
638
- import { createDashboardLogClickDeps } from './log-click-runtime.js';
671
+ import { createDashboardLogClickDeps, createFoldClickScrollAnchor } from './log-click-runtime.js';
639
672
  import { runDashboardSidebarSessionJoin } from './sidebar-session-join-runtime.js';
640
673
  import {
641
674
  runDashboardAutoRouteCountdown,
@@ -791,6 +824,7 @@ import { bootBannerStatusLines, normalizeBootBannerMode, renderMinimalBootBand,
791
824
  import { attachDashboardFilePathToken } from './file-path-attachment.js';
792
825
  import { bootDashboardSlashExecutor } from './slash-executor-boot.js';
793
826
  import { buildDashboardSlashRegistry, type DashboardSlashContext } from './slash-runtime/index.js';
827
+ import type { LoopScheduler } from './loop-scheduler.js';
794
828
  import { unknownSlashReply } from './slash-runtime/unknown-slash-reply.js';
795
829
  import { resolveSurfaceUx } from '../agent/surface-ux/build.js';
796
830
  import type { SurfaceUx } from '../agent/surface-ux/types.js';
@@ -846,7 +880,9 @@ import {
846
880
  getGitStatusView,
847
881
  subscribeGitChanges,
848
882
  refreshDirtyAsync as refreshGitDirtyAsync,
883
+ refreshGitSnapshotAsync,
849
884
  } from '../git-fs/index.js';
885
+ import { refreshSelfAmbientCache } from '../agent/self-ambient-cache.js';
850
886
  import { getPty, listPty, requestPtyTakeover } from '../pty-shell/registry.js';
851
887
  import { listPtyManifestRowsAt } from '../pty-shell/pty-manifest.js';
852
888
  import { ptyManifestTargets } from '../domains/fleet.js';
@@ -984,13 +1020,19 @@ import {
984
1020
  } from '../render.js';
985
1021
  import type { SyncMode } from '../types.js';
986
1022
  import type { ChatMessage } from '../chat/index.js';
1023
+ import { repairCodexToolPairs } from './input/compact-prepass-recovery.js';
1024
+ import { observeSessionStart } from '../session/session-start.js';
987
1025
  import type { PlainTurnSubmit } from '../input/turn-submit.js';
988
1026
  // U-4.2 · `renderLogPane` no longer called from dashboard directly —
989
1027
  // wd-log widget owns log rendering via renderLogViaWidget. Only
990
1028
  // `findBlock` still used here (chat-input block navigation).
991
1029
  import { findBlock, logPaneContentHeight, maxLogPaneScroll, visibleSearchRowOffset } from '../panes/log-pane.js';
992
1030
  import { createQuitConfirmationState, gracefulQuit, type QuitConfirmationState } from './graceful-quit.js';
993
- import { handoffTurnTypeaheadAtTurnEnd, releaseTurnTypeaheadPending, steerLastTurnTypeaheadSubmission } from './input/turn-steer-bridge.js';
1031
+ import { consumeTurnEndInterrupted, handoffTurnTypeaheadAtTurnEnd, releaseTurnTypeaheadPending, runTurnTypeaheadPushNow, steerLastTurnTypeaheadSubmission } from './input/turn-steer-bridge.js';
1032
+ import { abortTurnOnly } from '../turn-abort-scope.js';
1033
+ import { consumeInternalTurnYield, createInternalTurnGate, formatInternalTurnBanner, resolveInternalTurnSubmitIntent, type InternalTurnRequest } from './input/internal-turn.js';
1034
+ import { createSeatAutowake } from './input/seat-autowake.js';
1035
+ import { createDashboardWakeupScheduler, registerScheduleWakeupHostTool } from '../skills/tools/schedule-wakeup.js';
994
1036
  import { createAttachmentRowMap } from '../log-pane/attachment-row-map.js';
995
1037
  import { createAttachmentPopup, type AttachmentPopupAction } from '../log-pane/attachment-popup.js';
996
1038
  import { findLogMatches, findVisibleLogMatches, type LogSearchResult } from '../log-pane/search.js';
@@ -1022,7 +1064,6 @@ import { enqueueSoftStop, readSoftStopRequestStatus } from '../harness/control-i
1022
1064
  import { setSelfImplementSoftStopSpaceObserver } from '../self-implement/self-implement-runtime.js';
1023
1065
  import { createViEditor, type ViEditorHandle } from '../vi-editor.js';
1024
1066
  import { launchEditor, canLaunchEditor } from '../editor-launcher.js';
1025
- import { createSshPickerModal } from '../ssh/ssh-picker-modal.js';
1026
1067
  import {
1027
1068
  refreshRemoteWorkingDir,
1028
1069
  refreshRemoteWorkingDirPreview,
@@ -1032,12 +1073,10 @@ import {
1032
1073
  remoteBadge,
1033
1074
  } from '../ssh/remote-browser.js';
1034
1075
  import { editRemoteFile } from '../ssh/remote-edit.js';
1035
- import { scanFinder, relativizeResults } from '../finder/finder-scan.js';
1036
- import { scanRemoteFinder, relativizeRemoteResults } from '../finder/finder-scan-remote.js';
1037
- import { createFinderModal, createFolderPickerModal, type FinderItem } from '../finder/finder-modal.js';
1038
- import { homedir, hostname } from 'node:os';
1039
- import { listTransferTargets, type TransferTarget } from '../transfer/transfer-targets.js';
1040
- import { createTransferPickerModal } from '../transfer/transfer-picker-modal.js';
1076
+ import { createFolderPickerModal } from '../finder/finder-modal.js';
1077
+ import { hostname } from 'node:os';
1078
+ import { type TransferTarget } from '../transfer/transfer-targets.js';
1079
+ import { createInputPickers } from '../tui/input/pickers.js';
1041
1080
  import { sshTransfer, type SshTransferProgress } from '../transfer/ssh-transfer.js';
1042
1081
  import { iphoneTransfer } from '../transfer/iphone-transfer.js';
1043
1082
  import { DashboardAcpChat, displayBackendName, parseAcpSlash, type AcpBackendId } from './chat/acp-chat.js';
@@ -1081,7 +1120,6 @@ import {
1081
1120
  } from '../agent/channel-router.js';
1082
1121
  import { initTtySnapshotTools } from '../skills/tools/tty-snapshot.js';
1083
1122
  import { initAgentHandoffTool } from '../skills/tools/agent-handoff.js';
1084
- import { touchSshHost } from '../ssh/ssh-hosts.js';
1085
1123
  import { DisplayCoordinator } from '../display/coordinator.js';
1086
1124
  import {
1087
1125
  createLivePlaygroundHarness,
@@ -1115,6 +1153,8 @@ import { agentBatchScratchTitle, renderAgentBatchScratch } from '../display/agen
1115
1153
  import { type ExecutionSurfaceHandle } from '../display/index.js';
1116
1154
  import { AgentSurfaceStore, renderAgentDetail, renderAgentRoster, type AgentSurfaceState } from '../display/agent-surface.js';
1117
1155
  import { globalAgentFlash } from '../display/agent-flash.js';
1156
+ import { globalTaskNotificationQueue } from '../agent/task-notification.js';
1157
+ import { createDashboardAgentWakeNotify, installAgentWake, wrapAgentWakePort } from './input/agent-wake.js';
1118
1158
  import { renderDebugEventDetail, renderDebugEventRows, renderDebugStack, renderPromptInjectionDebug } from '../display/debug-surface.js';
1119
1159
  import { buildDebugCallStack } from '../debug/call-stack.js';
1120
1160
  import { buildAstGrepHostTool } from '../skills/tools/ast-grep.js';
@@ -1162,7 +1202,6 @@ import {
1162
1202
  encodeBrowserScopedSubmitText,
1163
1203
  resolveBrowserActionContextFromHit,
1164
1204
  } from '../browser-pane/actions.js';
1165
- import { resolveBrowserTransferCapability } from '../browser-pane/capabilities.js';
1166
1205
  import {
1167
1206
  cyclePreviewSourceForView,
1168
1207
  formatPreviewSourceLabel,
@@ -1276,7 +1315,7 @@ import {
1276
1315
  type SpaceLongPressDetector,
1277
1316
  } from './input/space-longpress-detector.js';
1278
1317
  import { isAllowlistedDictationSurface } from './input/dictation-surface-allowlist.js';
1279
- import { createDashboardExternalInputHost } from './input/external-input-host.js';
1318
+ import { createDashboardExternalInputHost, stopTurnViaEscGate, updateDashboardExternalInputQueueNotice } from './input/external-input-host.js';
1280
1319
  import { registerExternalInputHost } from '../input/external-input-queue.js';
1281
1320
  // H3 #6 follow-up #3 — background sessions render as a distinct sidebar
1282
1321
  // kind with live state badge + tap-to-Join. Dashboard pulls BG records
@@ -1882,8 +1921,11 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
1882
1921
  const dashboardQuitConfirmationState = createQuitConfirmationState();
1883
1922
  const signalQuitConfirmationState = createQuitConfirmationState();
1884
1923
  let disposeDashboardEventLoopMonitor = (): void => {};
1924
+ // TUI-AGENT-IDLE-WAKE — 이 대시보드 «자기» 완료 알림 구독만 푼다(배선 지점에서 채운다).
1925
+ let disposeDashboardAgentWake = (): void => {};
1885
1926
  const closeDashboardTui = (): void => {
1886
1927
  disposeDashboardEventLoopMonitor();
1928
+ try { disposeDashboardAgentWake(); } catch { /* never block exit */ }
1887
1929
  closeTui();
1888
1930
  };
1889
1931
  const exitDashboardTui = (): void => {
@@ -2202,6 +2244,7 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
2202
2244
  pillRowGetter: () => pillRowGetterForClick?.() ?? null,
2203
2245
  onPillClick: () => onPillClickForClick?.(),
2204
2246
  foldStack,
2247
+ foldClickAnchor,
2205
2248
  });
2206
2249
 
2207
2250
  // Dispatcher for attachment-popup actions (log-pane click → popup
@@ -2793,6 +2836,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2793
2836
  // execute slashes, etc.
2794
2837
  const chatModeState = createSessionPostureState();
2795
2838
 
2839
+ // TUI-LOOP-COMMAND — 실행 중 스케줄러를 쥐는 ref. showDashboard 인스턴스당
2840
+ // 하나로, 입력 루프 «밖»에서 한 번만 선언한다. slashCtx 는 명령마다 다시
2841
+ // 만들어지므로 리터럴 옆에 선언하면 /loop start 로 만든 스케줄러 ref 가
2842
+ // 다음 명령에서 사라진다 — /loop stop · /session new|clear 세션 끝 정지가
2843
+ // 같은 ref 를 써야 «stop 0»(판정)가 성립한다.
2844
+ const dashboardLoopSchedulerRef: { current: LoopScheduler | null } = { current: null };
2845
+
2796
2846
  // T3-C2 — vi-editor mounted in the preview pane. Set by the
2797
2847
  // browser pane's `e` key on a file; cleared on :q / :q!. When
2798
2848
  // non-null it overrides the preview pane's normal render path.
@@ -2865,6 +2915,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2865
2915
  // current value so log analysis can filter "what keys fired while
2866
2916
  // a turn was in flight".
2867
2917
  let streamingInFlight = false;
2918
+ /** 지금 턴의 escGate 중단 — 외부 `/stop`(TUI-STOP-INPUT-BYPASS)이 대기열을 건너뛰어 부른다. 턴 밖이면 null. */
2919
+ let currentTurnExternalStop: (() => void) | null = null;
2868
2920
  const resolveTmuxStatusLabel = (): string | null => {
2869
2921
  if (!process.env['TMUX']) return null;
2870
2922
  const fallback = process.env['TMUX_PANE'] ?? 'tmux';
@@ -3333,6 +3385,31 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3333
3385
  // 즉시 에코 · 턴 종료 시 loop-head 핸드오프가 prefill/자동제출로 넘긴다.
3334
3386
  // (외곽 스코프 선언 — 스트리밍 디스패처와 메인 키 루프 양쪽에서 참조.)
3335
3387
  const turnTypeaheadRef = { state: createTurnTypeaheadState() };
3388
+ // «지금 밀어 넣기»로 끊은 턴 — 턴 종료가 큐를 composer 로 «복원»하지 않고 곧바로 «제출»하게 한다.
3389
+ const steerInterruptRef = { pending: false };
3390
+ // ⭐ H2 내부 턴 문(TUI-H2-INTERNAL-TURN) — 자율 재개(H3·H4·H5)가 지나갈 «문 하나». 판정은 모듈,
3391
+ // 여기선 상태를 읽어 넘기기만 한다(⚠️ promptCtl·가시성 함수는 아래에서 선언 — 호출 시점엔 있다).
3392
+ const internalTurnGate = createInternalTurnGate({
3393
+ isTurnRunning: () => streamingInFlight,
3394
+ isInputForeground: () =>
3395
+ chatMainInputVisibilityState().inputOwnership?.owner === 'chat-main' && !topBlockingForegroundModal(),
3396
+ getBufferText: () => promptCtl.getBufferText?.(),
3397
+ getTypeaheadCount: () =>
3398
+ turnTypeaheadRef.state.queuedSubmissions.length + (turnTypeaheadRef.state.buffer ? 1 : 0),
3399
+ });
3400
+ // ⭐ H3 턴 끝 자동 재개(OPT-QUEUE-AUTOWAKE) — 자리 TUI ∧ autowake 파일일 때만. 턴 끝에 «남은 일»(안 읽은
3401
+ // 우편 · queue.md «## 대기»)을 재고 H2 문으로 다음 턴을 청한다. 판정·상한은 모듈, 여기선 배선만.
3402
+ const seatAutowake = createSeatAutowake({
3403
+ startTurnIfIdle: (text, opts) => internalTurnGate.startTurnIfIdle(text, opts),
3404
+ getCwd: () => workingDir.cwd,
3405
+ notify: (line) => { chatLines.push(C.muted(line)); draw(); },
3406
+ });
3407
+ const internalTurnYieldPort = wrapAgentWakePort(seatAutowake.wrapPort(internalTurnGate.port));
3408
+ // ⭐ H5 자기 예약 깨움(TUI-H5-SCHEDULE-WAKEUP) — 프로세스 안 타이머가 그 시각에 H2 문으로 한 턴을 청한다.
3409
+ // 직전 회차 판정 = 그 예약이 연 내부 턴이 아직 도나. 도구 등록은 아래 host tool 자리(하니스 자식 제외).
3410
+ const wakeupScheduler = createDashboardWakeupScheduler(internalTurnGate, {
3411
+ notify: (line) => { chatLines.push(C.muted(line)); draw(); },
3412
+ });
3336
3413
  // ⭐ `B3` — 큐를 «도는 턴»에 넣으려면 그 턴이 도는 ***ACP 세션 id*** 가 필요하다.
3337
3414
  // ⛔ `attachedSessionId`(대화 세션)와 «다른 축»이다 — 루프는 ACP 세션으로 스코프를 연다.
3338
3415
  // ⚠️ 세션은 이 함수 «뒤»에서 만들어지므로 참조로 둔다(호출 시점엔 채워져 있다).
@@ -3361,6 +3438,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3361
3438
  });
3362
3439
  }
3363
3440
  try { process.stdout.write(paint); } catch { /* fail-soft */ }
3441
+ noteInputLagPaint();
3364
3442
  };
3365
3443
 
3366
3444
  // Use this instead of bare `chatScrollOffset = -1` inside streaming
@@ -3390,9 +3468,17 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3390
3468
  }),
3391
3469
  log: (category, event, data) => debug.log(category, event, data),
3392
3470
  });
3471
+ // ⌨️ TUI-TYPING-LAG-OBS — 키→칠 ms ⊕ 턴 시작 창 루프 지연(`tui.input-lag`). 훅은 tui.ts·chat·turn-preamble.
3472
+ const inputLagProbe = createInputLagProbe({
3473
+ isBusy: () => streamingInFlight,
3474
+ log: (category, event, data) => debug.log(category, event, { ...data }),
3475
+ });
3476
+ setInputLagProbe(inputLagProbe);
3393
3477
  const disposeEventLoopStallMonitorOnBeforeExit = (): void => disposeDashboardEventLoopMonitor();
3394
3478
  disposeDashboardEventLoopMonitor = (): void => {
3395
3479
  disposeEventLoopStallMonitor();
3480
+ setInputLagProbe(null);
3481
+ inputLagProbe.dispose();
3396
3482
  process.removeListener('beforeExit', disposeEventLoopStallMonitorOnBeforeExit);
3397
3483
  };
3398
3484
  process.once('beforeExit', disposeEventLoopStallMonitorOnBeforeExit);
@@ -3401,14 +3487,24 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3401
3487
  // batch status during skill execution, plus static folds already
3402
3488
  // committed to chatLines. The runtime helper owns registration;
3403
3489
  // `f` just toggles the most recent target.
3490
+ // A click toggle keeps the viewport top (#81 — pinning to the tail
3491
+ // moved the clicked header, so the same spot no longer folded back).
3492
+ const foldClickAnchor = createFoldClickScrollAnchor({
3493
+ getScrollOffset: () => chatScrollOffset,
3494
+ setScrollOffset: (offset) => { chatScrollOffset = offset; },
3495
+ maxScroll: () => logScrollMax(computeLogH(termSize().rows)),
3496
+ getFrozenTail: () => logFrozenTailIndex,
3497
+ setFrozenTail: (index) => { logFrozenTailIndex = index; },
3498
+ lineCount: () => chatLines.length,
3499
+ }, () => {
3500
+ // Pin back to tail if the user hadn't scrolled away — expanding
3501
+ // a batch tree adds rows, and losing the bottom anchor is
3502
+ // disorienting when `f` was supposed to reveal more content.
3503
+ chatScrollOffset = -1;
3504
+ });
3404
3505
  const foldStack = new FoldStack({
3405
3506
  chatLines,
3406
- onAfterToggle: () => {
3407
- // Pin back to tail if the user hadn't scrolled away — expanding
3408
- // a batch tree adds rows, and losing the bottom anchor is
3409
- // disorienting when `f` was supposed to reveal more content.
3410
- chatScrollOffset = -1;
3411
- },
3507
+ onAfterToggle: (target, delta) => foldClickAnchor.onAfterToggle(target, delta),
3412
3508
  });
3413
3509
  // The most recent streamed assistant response — kept in both raw and
3414
3510
  // formatted form so Ctrl+Y can copy clean markdown and `r` in log
@@ -3640,7 +3736,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3640
3736
  // boot block via bgSurface.onUpdate; read on every status redraw.
3641
3737
  // Null until the first handle is tracked so we don't render an empty
3642
3738
  // pill on a fresh session.
3643
- let latestShellRollup: { running: number; backgrounded: number } | null = null;
3739
+ let latestShellRollup: { running: number; backgrounded: number; background: number } | null = null;
3740
+ // TUI-BG-JOB-VISIBILITY (#71) — «⧗ 백그라운드 N»(턴을 넘어 사는 셸 일). 관측은 모듈, 여기선 배선만
3741
+ // (깨우기 0 — TUI-AGENT-IDLE-WAKE 몫).
3742
+ const backgroundJobsStatus = createBackgroundJobsStatus();
3644
3743
  // The ACP bridge is created later during dashboard boot. Keep status renders
3645
3744
  // safe before then, and bind this reader once the bridge exists.
3646
3745
  let activeAcpRuntimeModel = (): string | undefined => undefined;
@@ -3680,6 +3779,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3680
3779
  sshLabel: sshStatusLabel,
3681
3780
  host: hostLabel,
3682
3781
  runningAgents: globalAgentRegistry.list().filter((agent) => agent.state === 'running').length,
3782
+ backgroundJobs: backgroundJobsStatus.summary(),
3683
3783
  controller: process.env.ELANOUS_CONTROLLER?.trim() || undefined,
3684
3784
  shellCount: liveShells,
3685
3785
  shellRollup: latestShellRollup ?? undefined,
@@ -3707,7 +3807,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3707
3807
  let drawQueued = false;
3708
3808
  let queuedDrawForce = false;
3709
3809
  let lastDrawDebugAt = 0;
3710
- let repaintPromptAfterRender: (opts?: { force?: boolean }) => void = () => {};
3810
+ const promptRowOwner = createPromptRowOwner({ requestFrame: () => draw() });
3811
+ const observePromptBand = createPromptBandRenderObserver();
3711
3812
  const displayEvents = createDisplayEventBus();
3712
3813
  const recentTerminalMouseIntents: Array<{
3713
3814
  surfaceId: string;
@@ -3864,11 +3965,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3864
3965
  // and fires onPush/onPop. The modal-surface-adapter (registered
3865
3966
  // below after widgetHost discovery) fans those into the global
3866
3967
  // SurfaceRegistry. Existing pushModal callsites unchanged.
3867
- const identityHooks = composeIdentityHooks({
3868
- afterRender: (request) => {
3869
- try { repaintPromptAfterRender({ force: request.force }); } catch { /* noop */ }
3870
- },
3871
- });
3968
+ const identityHooks = composeIdentityHooks({});
3872
3969
  // U4 Bundle B — layer the workspace host wiring on top of modal
3873
3970
  // identity wiring so popup/dialog/terminal modal mounts
3874
3971
  // automatically project into a host-managed workspace set. This is
@@ -4050,7 +4147,17 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4050
4147
  // out-of-process modifications (external editor, another PTY,
4051
4148
  // etc.). Throttle inside git-fs prevents back-to-back subprocess
4052
4149
  // spawns when this and a post-Edit refresh overlap.
4150
+ // TUI-TYPING-LAG — keep the turn-preamble caches warm off the event loop (boot ⊕ every tick):
4151
+ // the git snapshot (async subprocesses) and the self-ambient store scan (worker thread). A turn
4152
+ // then serves them from cache instead of rebuilding synchronously on the turn path. Both are
4153
+ // no-ops while the cached value is younger than the threshold; failures are fail-soft.
4154
+ const warmTurnPreambleCaches = (): void => {
4155
+ try { void refreshGitSnapshotAsync(getSessionCwd(), { ifOlderThanMs: 30_000 }); } catch { /* noop */ }
4156
+ try { void refreshSelfAmbientCache({}, { ifOlderThanMs: 25_000 }); } catch { /* noop */ }
4157
+ };
4158
+ warmTurnPreambleCaches();
4053
4159
  const gitDirtyTimer: ReturnType<typeof setInterval> = setInterval(() => {
4160
+ warmTurnPreambleCaches();
4054
4161
  try {
4055
4162
  const before = getGitStatusView(getSessionCwd());
4056
4163
  // TUI-TYPING-LAG — the 5s probe ran `git status` synchronously (130ms+, hundreds under load).
@@ -4176,7 +4283,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4176
4283
  wireShellRunnerSurface,
4177
4284
  createFileCaptureEngine,
4178
4285
  setShellRunnerDeps,
4179
- setLatestShellRollup: (rollup) => { latestShellRollup = rollup; },
4286
+ setLatestShellRollup: (rollup) => {
4287
+ latestShellRollup = rollup;
4288
+ // 유휴 중 백그라운드가 끝나도 상태줄이 고쳐지도록 — 수가 바뀔 때만 다시 그린다.
4289
+ if (backgroundJobsStatus.noteRollup(rollup)) {
4290
+ try { draw(); } catch { /* TUI torn down */ }
4291
+ }
4292
+ },
4180
4293
  });
4181
4294
  }
4182
4295
  const { setShellApprover } = await import('../shell-primitive/index.js');
@@ -6458,8 +6571,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6458
6571
  const attachChatStreamingKeys = (abortCtrl: AbortController): () => void => {
6459
6572
  const chatLinesStartIndex = chatLines.length;
6460
6573
  userScrolledDuringStream = false;
6461
- streamingInFlight = true;
6462
- reportTuiTurn('busy');
6463
6574
  const SCROLL_KEYS = new Set(['j', 'k', 'down', 'up', 'pageup', 'pagedown', 'home', 'end', 'g', 'G']);
6464
6575
  // T3-B2: ESC abort gate. When the user hits Esc AND sub-agent
6465
6576
  // work is running, pop a confirmation modal before tearing down
@@ -6759,7 +6870,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6759
6870
  // ⛔ 중복 전송 방지가 계약이다: 레지스트리가 배수되면 `onDrained` 가
6760
6871
  // ***여기 큐도 같이 비운다*** — 안 그러면 같은 발화가 턴 끝에 «또» 나간다.
6761
6872
  // ⚠️ `immediate`(슬래시 등)는 지금 실행되므로 «넣지 않는다».
6762
- if (r.submissionDisposition === 'fifo' && !r.immediateSubmission) {
6873
+ // ⛔ H2 — 내부 턴 중의 사람 입력은 그 턴에 끼워 넣지 않는다(턴 끝 FIFO 로 기다린다).
6874
+ if (r.submissionDisposition === 'fifo' && !r.immediateSubmission && !internalTurnGate.isInternalTurnActive()) {
6763
6875
  const queuedText = turnTypeaheadRef.state.queuedSubmissions.at(-1);
6764
6876
  const sid = activeSessionIdForTypeahead();
6765
6877
  if (queuedText && sid) {
@@ -6790,6 +6902,22 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6790
6902
  }
6791
6903
  }
6792
6904
  if (r.releasedPendingIds) releaseTurnTypeaheadPending(r.releasedPendingIds, 'recalled');
6905
+ if (r.pushNowRequested) {
6906
+ const decision = runTurnTypeaheadPushNow(turnTypeaheadRef, {
6907
+ runningToolNames: () => getEscAbortWaitingToolNames(escAbortToolState),
6908
+ interruptTurn: () => {
6909
+ steerInterruptRef.pending = true;
6910
+ try { globalAgentRegistry.markBackgroundAllRunning(); } catch { /* 승격 실패가 끊기를 막지 않는다 */ }
6911
+ abortTurnOnly(abortCtrl);
6912
+ },
6913
+ pushNotice: (line) => pushChatLine(C.muted(line)),
6914
+ });
6915
+ debug.log('dashboard.turn-typeahead', 'push-now', {
6916
+ kind: decision.kind,
6917
+ tools: decision.kind === 'interrupt' ? decision.runningTools : decision.kind === 'nothing' ? [] : decision.blockingTools,
6918
+ });
6919
+ draw();
6920
+ }
6793
6921
  if (r.immediateSubmission) {
6794
6922
  turnTypeaheadRef.state = await dispatchStreamingTurnTypeaheadSubmission(
6795
6923
  turnTypeaheadRef.state,
@@ -7015,19 +7143,29 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
7015
7143
  // `runStreamingKeyUnifiedDispatch` above for the route body.
7016
7144
  await runStreamingKeyUnifiedDispatch(key);
7017
7145
  });
7146
+ // ⛔ TUI-STALE-BUSY-MARKER — busy 는 «끄는 closure 를 돌려주기 직전»에 켠다. 위 준비(ESC 게이트·키 리스너)가
7147
+ // 던지면 호출자는 cleanup 을 못 받으므로, 먼저 켜 두면 아무도 못 끄는 busy 가 남는다(#37).
7148
+ streamingInFlight = true;
7149
+ reportTuiTurn('busy');
7150
+ const externalStop = (): void => { stopTurnViaEscGate(escGate); };
7151
+ currentTurnExternalStop = externalStop;
7018
7152
  return () => {
7019
- cleanup();
7020
- escGate.dispose();
7153
+ if (currentTurnExternalStop === externalStop) currentTurnExternalStop = null;
7154
+ // 단계마다 격리 — 앞 단계가 던져도 settle·idle 전이에 닿는다(키 층 유휴 ∧ 턴 층 busy 이원 판정 #55 차단).
7155
+ runTurnTeardownStep('stream-keys-detach', cleanup);
7156
+ runTurnTeardownStep('esc-gate-dispose', () => { escGate.dispose(); });
7021
7157
  try {
7022
7158
  if (!stoppedHarnessSpace) settleEscAbortPendingLines(chatLines, chatLinesStartIndex);
7023
7159
  } catch (err) {
7024
7160
  const message = err instanceof Error ? err.message : String(err);
7025
7161
  const stackHead = err instanceof Error ? err.stack?.split('\n')[1]?.trim() : undefined;
7026
7162
  debug.log('dashboard.turn', 'settle-failed', { message, stackHead });
7163
+ logTurnThrew('settle-esc-lines', err);
7027
7164
  } finally {
7028
7165
  userScrolledDuringStream = false;
7029
7166
  streamingInFlight = false;
7030
7167
  reportTuiTurn('idle');
7168
+ seatAutowake.noteTurnEnded();
7031
7169
  }
7032
7170
  };
7033
7171
  };
@@ -9889,6 +10027,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
9889
10027
  // kept alive per cwd; reaped on 10-min idle. See
9890
10028
  // 내부 문서 `CAPABILITIES-lsp` for the op surface.
9891
10029
  pluginHost.registerHostTool(buildLspHostTool());
10030
+ // H5 — 이 TUI 로 돌아오는 자기 예약. 하니스 자식(ELANOUS_HARNESS_ROLE 있음)은 무인 런이라 싣지 않는다.
10031
+ registerScheduleWakeupHostTool(pluginHost, wakeupScheduler);
9892
10032
  pluginHost.registerHostTool({
9893
10033
  name: 'view_getConfig',
9894
10034
  description: 'Return dashboard view configuration: native and custom views, order, enabled state, shortcuts, baseView, pane rows, ratios, primary pane, and active view.',
@@ -11216,6 +11356,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11216
11356
  // actually scrolled up. If something else (streaming, slash reply)
11217
11357
  // pulls the offset back to -1 (tail), the freeze is no-op'd so the
11218
11358
  // user sees full content at tail regardless of stale index state.
11359
+ foldClickAnchor.reconcile();
11219
11360
  const logProjection = buildDashboardLogProjection({
11220
11361
  chatScrollOffset,
11221
11362
  logFrozenTailIndex,
@@ -11276,7 +11417,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11276
11417
  height: 1,
11277
11418
  render: (_h, c) => [suppressPromptArea ? ' '.repeat(c) : hLine(c)],
11278
11419
  });
11279
- const inputPromptZone = buildDashboardTurnTypeaheadPromptZone({
11420
+ const inputPromptZone = withPromptRowOwner(buildDashboardTurnTypeaheadPromptZone({
11280
11421
  frame: promptFrame,
11281
11422
  height: promptFrame.inputHeight,
11282
11423
  placeholder: C.muted('\u276f '),
@@ -11286,7 +11427,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11286
11427
  width: totalW,
11287
11428
  suppressPromptArea,
11288
11429
  onPaint: paint => { promptCaret = paint.caret; },
11289
- });
11430
+ }), promptRowOwner, { suppressPromptArea, onCaret: (caret) => { promptCaret = caret; }, observe: observePromptBand });
11290
11431
  zones.push(observeDashboardTurnTypeaheadPromptZone(inputPromptZone, {
11291
11432
  promptBottomRow: promptFrame.promptBottomRow,
11292
11433
  state: turnTypeaheadRef.state,
@@ -11298,7 +11439,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11298
11439
  });
11299
11440
 
11300
11441
  // Essential bottom area: status line only; no dock row.
11301
- zones.push({ id: 'status-gap-top', height: 1, render: () => [''] });
11442
+ zones.push({ id: 'status-gap-top', height: 1, render: () => [renderPromptOwnerHintRow(promptRowOwner, { suppressPromptArea })] });
11302
11443
  zones.push({
11303
11444
  id: 'status',
11304
11445
  height: 1,
@@ -11519,11 +11660,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11519
11660
  if (debug.enabled) {
11520
11661
  debug.log('dashboard.draw', 'flush-microtask', {
11521
11662
  force,
11522
- promptRepainted: true,
11663
+ promptRepainted: false,
11523
11664
  });
11524
11665
  }
11525
11666
  drawNow({ force });
11526
- try { repaintPromptAfterRender({ force }); } catch { /* noop */ }
11527
11667
  });
11528
11668
  };
11529
11669
 
@@ -11561,63 +11701,53 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11561
11701
  let agentSearchModal: SearchModalHandle | null = null;
11562
11702
  let agentSearchHandle: { dispose: () => void } | null = null;
11563
11703
 
11564
- /** Open the agent-roster search modal. Reads the current roster
11565
- * from agentSurfaceStore + globalAgentRegistry. Items: each
11566
- * entry's name + last 8 chars of id. Enter sets cursor; Esc
11567
- * dismisses. */
11568
- function openAgentRosterSearch(): void {
11569
- if (agentSearchModal) return;
11570
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11571
- const cols = termSize().cols;
11572
- const modalWidth = Math.min(60, cols - 4);
11573
- const modalRow = 3; // top-anchored over the roster pane
11574
- const modalCol = Math.max(2, Math.floor((cols - modalWidth) / 2));
11575
- const filterable = roster.length > 3;
11576
- const handle = createSearchModal({
11577
- id: `agent-roster-search:${Date.now().toString(36)}`,
11578
- bounds: { row: modalRow, col: modalCol, width: modalWidth, height: 12 },
11579
- title: 'Search agents',
11580
- width: modalWidth,
11581
- maxVisible: 8,
11582
- primaryActionLabel: 'jump',
11583
- cancelActionLabel: 'cancel',
11584
- filterable,
11585
- actionButtons: true,
11586
- chromeSpec: {
11587
- titleAlign: 'center',
11588
- },
11589
- onQuery: (q) => {
11590
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11591
- const needle = q.trim().toLowerCase();
11592
- const match = (t: typeof roster[number]) => {
11593
- if (!needle) return true;
11594
- return t.name.toLowerCase().includes(needle)
11595
- || t.definitionName.toLowerCase().includes(needle)
11596
- || (t.summary ?? '').toLowerCase().includes(needle)
11597
- || t.id.toLowerCase().includes(needle);
11598
- };
11599
- return roster.filter(match).map((t): SearchItem => ({
11600
- label: `${t.name} ${t.definitionName} ${t.id.slice(0, 8)}`,
11601
- payload: t.id,
11602
- }));
11603
- },
11604
- onAccept: (item) => {
11605
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11606
- const idx = roster.findIndex(t => t.id === item.payload);
11607
- if (idx >= 0) {
11608
- agentRosterCursor = idx;
11609
- agentCursorLockedId = roster[idx]!.id;
11610
- agentCursorManual = true;
11611
- }
11612
- closeAgentRosterSearch();
11613
- },
11614
- onCancel: () => closeAgentRosterSearch(),
11615
- theme: currentThemeTokens(),
11616
- });
11617
- attachSurfaceToWorkspace(handle.surface, currentWorkspaceOwnerId());
11618
- agentSearchModal = handle;
11619
- agentSearchHandle = display.pushModal(handle.surface);
11620
- }
11704
+ // TUI-DIET-SEAM-PICKERS — the five input pickers (agent-roster search,
11705
+ // transfer, Ctrl+T file finder, Alt+C directory finder, Ctrl+K SSH)
11706
+ // live in src/tui/input/pickers.ts. Mutable dashboard state crosses
11707
+ // the seam as live get/set accessors (never a value copy); `draw` is
11708
+ // re-read at call time because it is reassigned after mount.
11709
+ const {
11710
+ openAgentRosterSearch,
11711
+ openTransferPicker,
11712
+ openFinderPicker,
11713
+ openDirectoryPicker,
11714
+ openSshPicker,
11715
+ } = createInputPickers({
11716
+ state: {
11717
+ get agentSearchModal() { return agentSearchModal; },
11718
+ set agentSearchModal(v: SearchModalHandle | null) { agentSearchModal = v; },
11719
+ get agentSearchHandle() { return agentSearchHandle; },
11720
+ set agentSearchHandle(v: { dispose: () => void } | null) { agentSearchHandle = v; },
11721
+ get agentRosterCursor() { return agentRosterCursor; },
11722
+ set agentRosterCursor(v: number) { agentRosterCursor = v; },
11723
+ get agentCursorLockedId() { return agentCursorLockedId; },
11724
+ set agentCursorLockedId(v: string) { agentCursorLockedId = v; },
11725
+ get agentCursorManual() { return agentCursorManual; },
11726
+ set agentCursorManual(v: boolean) { agentCursorManual = v; },
11727
+ get chatScrollOffset() { return chatScrollOffset; },
11728
+ set chatScrollOffset(v: number) { chatScrollOffset = v; },
11729
+ get chatLines() { return chatLines; },
11730
+ },
11731
+ display,
11732
+ agentSurfaceStore,
11733
+ workingDir,
11734
+ dockedPreview,
11735
+ transferFeedbackRuntime,
11736
+ finderFeedbackRuntime,
11737
+ currentThemeTokens,
11738
+ currentWorkspaceOwnerId,
11739
+ activeBrowserWidgetId,
11740
+ resolveBrowserStateById,
11741
+ fmtBytes,
11742
+ runTransfer,
11743
+ closeAgentRosterSearch,
11744
+ pushDebugLine,
11745
+ refreshRemotePreviewBridge,
11746
+ refreshWorkingDirPreview,
11747
+ setWorkingFocus,
11748
+ startRemoteModeBridge,
11749
+ draw: () => draw(),
11750
+ });
11621
11751
 
11622
11752
  function closeAgentRosterSearch(): void {
11623
11753
  if (agentSearchHandle) { agentSearchHandle.dispose(); agentSearchHandle = null; }
@@ -11625,75 +11755,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11625
11755
  draw();
11626
11756
  }
11627
11757
 
11628
- /** T5-J5 — open the transfer picker. Files arg = local paths to
11629
- * ship; destination comes from the picker. Handles both ssh and
11630
- * iphone target kinds. Remote-mode browser isn't supported here
11631
- * yet — the user is transferring LOCAL files (focused entry +
11632
- * selected set) to a remote destination. Transferring REMOTE→X
11633
- * is a separate flow (not this phase). */
11634
- function openTransferPicker(
11635
- files: Array<{ localPath: string; size?: number }>,
11636
- browserId: string = activeBrowserWidgetId(),
11637
- ): void {
11638
- const targetBrowser = resolveBrowserStateById(browserId) ?? (workingDir as BrowserPaneModel);
11639
- if (files.length === 0) {
11640
- transferFeedbackRuntime.onNoFilesToTransfer();
11641
- return;
11642
- }
11643
- const capability = resolveBrowserTransferCapability(
11644
- targetBrowser,
11645
- listTransferTargets(),
11646
- );
11647
- if (!capability.enabled) {
11648
- transferFeedbackRuntime.onTransferDisabled(capability.reason);
11649
- return;
11650
- }
11651
- const targets = capability.targets;
11652
- if (targets.length === 0) {
11653
- transferFeedbackRuntime.onNoTransferTargets();
11654
- return;
11655
- }
11656
- const totalBytes = files.reduce((a, f) => a + (f.size ?? 0), 0);
11657
- const summary = `${files.length} file${files.length === 1 ? '' : 's'}${
11658
- totalBytes > 0 ? ` (${fmtBytes(totalBytes)})` : ''
11659
- }`;
11660
-
11661
- const { cols, rows } = termSize();
11662
- const width = Math.min(74, Math.max(46, cols - 6));
11663
- const visibleRows = Math.min(10, targets.length);
11664
- const height = visibleRows + 3;
11665
- const bounds = {
11666
- row: Math.max(1, Math.floor((rows - height) / 2)),
11667
- col: Math.max(1, Math.floor((cols - width) / 2)),
11668
- width,
11669
- height,
11670
- };
11671
- let handle: ReturnType<typeof display.pushModal> | null = null;
11672
- const picker = createTransferPickerModal({
11673
- targets,
11674
- bounds,
11675
- width,
11676
- maxVisible: visibleRows,
11677
- summary,
11678
- theme: currentThemeTokens(),
11679
- onAccept: (target) => {
11680
- agentSearchModal = null;
11681
- handle?.dispose();
11682
- void runTransfer(target, files);
11683
- draw();
11684
- },
11685
- onCancel: () => {
11686
- agentSearchModal = null;
11687
- handle?.dispose();
11688
- draw();
11689
- },
11690
- });
11691
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
11692
- handle = display.pushModal(picker.surface);
11693
- agentSearchModal = picker;
11694
- draw();
11695
- }
11696
-
11697
11758
  async function runTransfer(
11698
11759
  target: TransferTarget,
11699
11760
  files: Array<{ localPath: string; size?: number }>,
@@ -11720,465 +11781,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11720
11781
  }
11721
11782
  }
11722
11783
 
11723
- /** T4-F2 / T4-F3 — open the Ctrl+T finder modal. Scans the
11724
- * current working dir (local only at MVP; remote scan is a
11725
- * follow-up). On accept, cd's the browser pane to the file's
11726
- * parent dir, focuses the file in the listing, and switches
11727
- * pane focus to browser so the user can e/enter next. */
11728
- async function openFinderPicker(): Promise<void> {
11729
- const targetBrowserId = activeBrowserWidgetId();
11730
- const targetBrowser = resolveBrowserStateById(targetBrowserId) ?? (workingDir as BrowserPaneModel);
11731
- // T5-I2 — branch on remote vs local. Both paths produce the
11732
- // same {absPath, relPath} finder items so the modal + onAccept
11733
- // handler stay unified.
11734
- let finderItems: FinderItem[] = [];
11735
- let backend = 'fd';
11736
- let truncated = false;
11737
- let scanDurationMs = 0;
11738
-
11739
- if (workingDir.remote) {
11740
- const host = workingDir.remote.host;
11741
- const remoteRoot = workingDir.remote.cwd;
11742
- finderFeedbackRuntime.onFinderScanStarted(`${host.name}:${remoteRoot}`);
11743
- draw();
11744
- const scan = await scanRemoteFinder({ host, root: remoteRoot });
11745
- const rels = relativizeRemoteResults(scan.paths, remoteRoot);
11746
- finderItems = scan.paths.map((absPath, i) => ({
11747
- absPath, relPath: rels[i] ?? absPath,
11748
- }));
11749
- backend = `${scan.backend}@${scan.host}`;
11750
- truncated = scan.truncated;
11751
- scanDurationMs = scan.durationMs;
11752
- } else {
11753
- // 2026-05-03 — scan from the Session Working Directory (the
11754
- // "project root"), NOT the browser pane's current navigation
11755
- // cursor. SWD is mutable only via Ctrl+W / /wd / SetWorkingDir
11756
- // tool — exactly the user's intent for "project root". Browser
11757
- // pane's cwd is purely UI navigation and should not narrow
11758
- // Ctrl+T's search scope (matches working-dir.ts:5-8 contract).
11759
- const scanRoot = getSessionCwd();
11760
- finderFeedbackRuntime.onFinderScanStarted(scanRoot);
11761
- draw();
11762
- const scan = await scanFinder({ root: scanRoot });
11763
- const rels = relativizeResults(scan.paths, scanRoot);
11764
- finderItems = scan.paths.map((absPath, i) => ({
11765
- absPath, relPath: rels[i] ?? absPath,
11766
- }));
11767
- backend = scan.backend;
11768
- truncated = scan.truncated;
11769
- scanDurationMs = scan.durationMs;
11770
- }
11771
- if (finderItems.length === 0) {
11772
- finderFeedbackRuntime.onFinderScanEmpty();
11773
- draw();
11774
- return;
11775
- }
11776
- finderFeedbackRuntime.onFinderScanCompleted({
11777
- count: finderItems.length,
11778
- backend,
11779
- truncated,
11780
- durationMs: scanDurationMs,
11781
- });
11782
-
11783
- const { cols, rows } = termSize();
11784
- const width = Math.min(90, Math.max(50, cols - 6));
11785
- const visibleRows = Math.min(15, Math.max(6, rows - 8));
11786
- const height = visibleRows + 3;
11787
- const bounds = {
11788
- row: Math.max(1, Math.floor((rows - height) / 2)),
11789
- col: Math.max(1, Math.floor((cols - width) / 2)),
11790
- width,
11791
- height,
11792
- };
11793
- let handle: ReturnType<typeof display.pushModal> | null = null;
11794
- // Title reflects the actual scan root — SWD for local (project
11795
- // root), remote cwd for remote mode. Browser pane's nav cursor
11796
- // is intentionally NOT shown because the scan ignores it.
11797
- const titleRoot = workingDir.remote
11798
- ? `${workingDir.remote.host.name}:${workingDir.remote.cwd}`
11799
- : getSessionCwd().replace(homedir(), '~');
11800
- // 2026-05-03 — preview modes (yazi/fzf-style, on-demand).
11801
- //
11802
- // Default mode is OFF — the picker opens cleanly with NO preview
11803
- // so it's never annoying on tablet / narrow terminals. The user
11804
- // toggles preview behaviour with `Ctrl+/` (matches zshrc
11805
- // `--bind 'ctrl-/:toggle-preview'`):
11806
- //
11807
- // off → hover → 2pane → off (cycle)
11808
- //
11809
- // • off — no preview, ever. The cursor moves freely.
11810
- // • hover — popup appears after the cursor stays still for
11811
- // 1 second (HOVER_DELAY_MS). Popup auto-dismisses
11812
- // after 4s. Centered, the same width as legacy
11813
- // behaviour but on-demand.
11814
- // • 2pane — browser-preview style: persistent preview popup
11815
- // docked to the right of the picker. Updates
11816
- // instantly as the cursor moves. Falls back to
11817
- // hover if the terminal is too narrow (<140 cols).
11818
- //
11819
- // showPreviewModal supports remote-skip by itself (preview
11820
- // handlers read local fs), so we simply guard with workingDir
11821
- // .remote before scheduling.
11822
- type PreviewMode = 'off' | 'hover' | '2pane';
11823
- const HOVER_DELAY_MS = 1000;
11824
- const TWO_PANE_MIN_COLS = 140;
11825
- const TWO_PANE_GROUP = 'finder-2pane-preview';
11826
-
11827
- let previewMode: PreviewMode = 'off';
11828
- let lastSelected: FinderItem | null = null;
11829
- let hoverTimer: ReturnType<typeof setTimeout> | null = null;
11830
- let lastPreviewHandle: { dispose(): void } | null = null;
11831
-
11832
- const clearHoverTimer = () => {
11833
- if (hoverTimer !== null) { clearTimeout(hoverTimer); hoverTimer = null; }
11834
- };
11835
- const disposeActivePreview = () => {
11836
- if (lastPreviewHandle) {
11837
- try { lastPreviewHandle.dispose(); } catch { /* ignore */ }
11838
- lastPreviewHandle = null;
11839
- }
11840
- };
11841
-
11842
- /** Compute the docked-preview bounds for 2-pane mode. Picker
11843
- * centred at width≈90; preview takes the right gutter. Returns
11844
- * null when there isn't enough room (caller falls back to
11845
- * hover). */
11846
- const computeDockedPreviewBounds = (): {
11847
- row: number; col: number; width: number; height: number;
11848
- } | null => {
11849
- const { cols: tc, rows: tr } = termSize();
11850
- if (tc < TWO_PANE_MIN_COLS) return null;
11851
- const pickerWidth = Math.min(90, Math.max(50, tc - 6));
11852
- const pickerCol = Math.max(1, Math.floor((tc - pickerWidth) / 2));
11853
- const previewCol = pickerCol + pickerWidth + 2;
11854
- const previewWidth = Math.max(40, tc - previewCol - 2);
11855
- if (previewWidth < 40) return null;
11856
- const previewHeight = Math.max(8, Math.min(tr - 6, 28));
11857
- const previewRow = Math.max(1, Math.floor((tr - previewHeight) / 2));
11858
- return { row: previewRow, col: previewCol, width: previewWidth, height: previewHeight };
11859
- };
11860
-
11861
- const paintPreviewFor = (item: FinderItem) => {
11862
- if (workingDir.remote) return; // remote previews unsupported
11863
- const { rows: tr, cols: tc } = termSize();
11864
- const docked = previewMode === '2pane' ? computeDockedPreviewBounds() : null;
11865
- void showPreviewModal(item.absPath, {
11866
- coordinator: display,
11867
- termCols: tc,
11868
- termRows: tr,
11869
- title: item.relPath,
11870
- ttlMs: previewMode === '2pane' ? 0 : 4000,
11871
- bounds: docked ?? undefined,
11872
- group: previewMode === '2pane' ? TWO_PANE_GROUP : 'image-preview',
11873
- }).then((r) => {
11874
- lastPreviewHandle = r?.handle ?? null;
11875
- }).catch(() => { /* preview failure is non-fatal */ });
11876
- };
11877
-
11878
- const scheduleHoverPreview = (item: FinderItem | null) => {
11879
- lastSelected = item;
11880
- clearHoverTimer();
11881
- if (previewMode === 'off') return;
11882
- if (!item || workingDir.remote) {
11883
- // Off-screen / remote — kill any straggling popup so the
11884
- // user doesn't see a preview that no longer matches the
11885
- // selection.
11886
- disposeActivePreview();
11887
- return;
11888
- }
11889
- if (previewMode === '2pane') {
11890
- // Instant update — no debounce. Persistent popup replaces
11891
- // itself via group dedup.
11892
- paintPreviewFor(item);
11893
- return;
11894
- }
11895
- // HOVER mode. Selection just changed → the popup currently on
11896
- // screen (if any) is stale. Drop it immediately so the user
11897
- // doesn't keep seeing the previous file's preview while their
11898
- // cursor is on a new file. The next paint waits HOVER_DELAY_MS
11899
- // of stillness before re-appearing.
11900
- disposeActivePreview();
11901
- hoverTimer = setTimeout(() => {
11902
- hoverTimer = null;
11903
- paintPreviewFor(item);
11904
- }, HOVER_DELAY_MS);
11905
- };
11906
-
11907
- /** Cycle off → hover → 2pane → off. Surfaces a status line so
11908
- * the user knows which mode they're in (Ctrl+/ has no chrome
11909
- * feedback otherwise). 2pane on a narrow terminal degrades
11910
- * to hover with a warning. */
11911
- const cyclePreviewMode = (): void => {
11912
- const next: PreviewMode = previewMode === 'off' ? 'hover'
11913
- : previewMode === 'hover' ? '2pane'
11914
- : 'off';
11915
- previewMode = next;
11916
- clearHoverTimer();
11917
- // Drop the active popup whenever the mode changes. The new
11918
- // mode (if any) re-paints below.
11919
- disposeActivePreview();
11920
-
11921
- if (next === 'off') {
11922
- chatLines.push(C.muted(' finder preview: OFF (Ctrl+/ to cycle modes)'));
11923
- } else if (next === 'hover') {
11924
- chatLines.push(C.muted(' finder preview: HOVER (popup after 1 s · Ctrl+/ next)'));
11925
- } else {
11926
- // 2pane — refuse if the terminal is too narrow; degrade to
11927
- // hover so the user gets *some* preview without overlap.
11928
- const docked = computeDockedPreviewBounds();
11929
- if (!docked) {
11930
- previewMode = 'hover';
11931
- chatLines.push(C.warning(` finder preview: 2-pane needs ≥${TWO_PANE_MIN_COLS} cols — falling back to HOVER`));
11932
- } else {
11933
- chatLines.push(C.muted(' finder preview: 2-PANE (docked right · Ctrl+/ to disable)'));
11934
- if (lastSelected) paintPreviewFor(lastSelected);
11935
- }
11936
- }
11937
- chatScrollOffset = -1;
11938
- draw();
11939
- };
11940
-
11941
- // Declared up here so `onAccept`/`onCancel` closures below can
11942
- // reference both; assigned right after the picker mounts.
11943
- let previewToggleDisposable: { dispose(): void } | null = null;
11944
- let previewDismissDisposable: { dispose(): void } | null = null;
11945
- const picker = createFinderModal({
11946
- items: finderItems,
11947
- bounds, width, maxVisible: visibleRows,
11948
- truncated,
11949
- title: `Find file in ${titleRoot}`,
11950
- theme: currentThemeTokens(),
11951
- onHover: scheduleHoverPreview,
11952
- onAccept: (item) => {
11953
- clearHoverTimer();
11954
- disposeActivePreview();
11955
- previewToggleDisposable?.dispose();
11956
- previewDismissDisposable?.dispose();
11957
- agentSearchModal = null;
11958
- handle?.dispose();
11959
- const parentDir = item.absPath.slice(0, item.absPath.lastIndexOf('/')) || '/';
11960
- const basename = item.absPath.slice(item.absPath.lastIndexOf('/') + 1);
11961
- if (workingDir.remote) {
11962
- // T5-I2 — remote cd + focus-by-basename.
11963
- enterRemoteDirectory(workingDir, parentDir);
11964
- (async () => {
11965
- await refreshRemoteWorkingDir(workingDir, {}, dockedPreview);
11966
- const idx = workingDir.entries.findIndex(e => e.name === basename);
11967
- if (idx >= 0) {
11968
- workingDir.cursor = idx;
11969
- workingDir.offset = Math.max(0, idx - 3);
11970
- }
11971
- await refreshRemotePreviewBridge();
11972
- pushDebugLine(C.muted(` ${workingDir.remote?.host.name}:${item.relPath}`));
11973
- chatScrollOffset = -1;
11974
- draw();
11975
- })().catch(() => {});
11976
- } else {
11977
- enterDirectory(targetBrowser as WorkingDirState, parentDir);
11978
- refreshWorkingDir(targetBrowser as WorkingDirState);
11979
- const idx = targetBrowser.entries.findIndex(e => e.name === basename);
11980
- if (idx >= 0) {
11981
- targetBrowser.cursor = idx;
11982
- targetBrowser.offset = Math.max(0, idx - 3);
11983
- }
11984
- refreshWorkingDirPreview();
11985
- pushDebugLine(C.muted(` ${item.relPath}`));
11986
- chatScrollOffset = -1;
11987
- }
11988
- setWorkingFocus('browser', 'agent-roster-search-pick');
11989
- draw();
11990
- },
11991
- onCancel: () => {
11992
- clearHoverTimer();
11993
- disposeActivePreview();
11994
- previewToggleDisposable?.dispose();
11995
- previewDismissDisposable?.dispose();
11996
- agentSearchModal = null;
11997
- handle?.dispose();
11998
- draw();
11999
- },
12000
- });
12001
- // Ctrl+/ (and Ctrl+_ which some terminals send for the same
12002
- // physical chord) cycles preview modes. Active for the lifetime
12003
- // of THIS picker only — disposed in both onAccept and onCancel
12004
- // above. Q4 (substrate Occam): no per-binding pipe alias, register
12005
- // each variant separately. (Korean IME aliases unnecessary — Ctrl+/
12006
- // has no jamo mapping on the 2-set layout.)
12007
- const togglePreviewMode = (): void => { cyclePreviewMode(); };
12008
- const previewSlash = display.registerKeyBinding({
12009
- id: `dashboard:finder-picker-preview-toggle-slash:${Date.now().toString(36)}`,
12010
- key: 'C-/',
12011
- scope: 'global',
12012
- when: () => agentSearchModal === picker,
12013
- handler: togglePreviewMode,
12014
- });
12015
- const previewUnder = display.registerKeyBinding({
12016
- id: `dashboard:finder-picker-preview-toggle-under:${Date.now().toString(36)}`,
12017
- key: 'C-_',
12018
- scope: 'global',
12019
- when: () => agentSearchModal === picker,
12020
- handler: togglePreviewMode,
12021
- });
12022
- previewToggleDisposable = {
12023
- dispose: () => { previewSlash.dispose(); previewUnder.dispose(); },
12024
- };
12025
- // Ctrl+] — instantly dismiss the active preview popup WITHOUT
12026
- // closing the picker or changing the mode. Esc would be more
12027
- // intuitive but the picker captures Esc internally to cancel
12028
- // itself. Ctrl+\ is unsafe because it raises SIGQUIT in many
12029
- // terminal drivers. Ctrl+] (historically "telnet escape" =
12030
- // "exit this context") is universally safe + semantically right.
12031
- // Active only while a preview is currently visible — otherwise
12032
- // the binding silently no-ops (no flicker, no log spam).
12033
- // Mode stays as set; if user is in HOVER mode, the next
12034
- // cursor stillness re-triggers the popup. To stop the popup
12035
- // from re-appearing entirely, cycle Ctrl+/ back to OFF.
12036
- previewDismissDisposable = display.registerKeyBinding({
12037
- id: `dashboard:finder-picker-preview-dismiss:${Date.now().toString(36)}`,
12038
- key: 'C-]',
12039
- scope: 'global',
12040
- when: () => agentSearchModal === picker && lastPreviewHandle !== null,
12041
- handler: () => {
12042
- clearHoverTimer();
12043
- disposeActivePreview();
12044
- draw();
12045
- },
12046
- });
12047
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12048
- handle = display.pushModal(picker.surface);
12049
- agentSearchModal = picker;
12050
- draw();
12051
- }
12052
-
12053
- /** Yazi-style Alt+C — directory finder. Symmetric with openFinder
12054
- * Picker but enumerates directories only (`scanFinder kind:'dir'`)
12055
- * and on accept cd's the browser pane to the chosen directory.
12056
- * Remote-mode is currently unsupported (surfaces a warning) — the
12057
- * remote finder would need a parallel kind-aware backend. */
12058
- async function openDirectoryPicker(): Promise<void> {
12059
- if (workingDir.remote) {
12060
- chatLines.push(C.warning(' Alt+C: directory finder is local-only — Esc remote first.'));
12061
- chatScrollOffset = -1;
12062
- draw();
12063
- return;
12064
- }
12065
- const targetBrowserId = activeBrowserWidgetId();
12066
- const targetBrowser = resolveBrowserStateById(targetBrowserId) ?? (workingDir as BrowserPaneModel);
12067
- // Symmetric with openFinderPicker — scan from SWD (project root),
12068
- // not the browser pane's nav cursor. The accept handler still
12069
- // mutates targetBrowser to navigate into the chosen dir.
12070
- const scanRoot = getSessionCwd();
12071
- finderFeedbackRuntime.onFinderScanStarted(`${scanRoot} (dirs)`);
12072
- draw();
12073
- const scan = await scanFinder({ root: scanRoot, kind: 'dir' });
12074
- const rels = relativizeResults(scan.paths, scanRoot);
12075
- const dirItems: FinderItem[] = scan.paths.map((absPath, i) => ({
12076
- absPath, relPath: rels[i] ?? absPath,
12077
- }));
12078
- if (dirItems.length === 0) {
12079
- finderFeedbackRuntime.onFinderScanEmpty();
12080
- draw();
12081
- return;
12082
- }
12083
- finderFeedbackRuntime.onFinderScanCompleted({
12084
- count: dirItems.length,
12085
- backend: scan.backend,
12086
- truncated: scan.truncated,
12087
- durationMs: scan.durationMs,
12088
- });
12089
-
12090
- const { cols, rows } = termSize();
12091
- const width = Math.min(90, Math.max(50, cols - 6));
12092
- const visibleRows = Math.min(15, Math.max(6, rows - 8));
12093
- const height = visibleRows + 3;
12094
- const bounds = {
12095
- row: Math.max(1, Math.floor((rows - height) / 2)),
12096
- col: Math.max(1, Math.floor((cols - width) / 2)),
12097
- width,
12098
- height,
12099
- };
12100
- let handle: ReturnType<typeof display.pushModal> | null = null;
12101
- const titleRoot = scanRoot.replace(homedir(), '~');
12102
- const picker = createFinderModal({
12103
- items: dirItems,
12104
- bounds, width, maxVisible: visibleRows,
12105
- truncated: scan.truncated,
12106
- title: `Find directory in ${titleRoot}`,
12107
- theme: currentThemeTokens(),
12108
- onAccept: (item) => {
12109
- agentSearchModal = null;
12110
- handle?.dispose();
12111
- enterDirectory(targetBrowser as WorkingDirState, item.absPath);
12112
- refreshWorkingDir(targetBrowser as WorkingDirState);
12113
- refreshWorkingDirPreview();
12114
- pushDebugLine(C.muted(` cd ${item.relPath}`));
12115
- chatScrollOffset = -1;
12116
- setWorkingFocus('browser', 'alt-c-dir-pick');
12117
- draw();
12118
- },
12119
- onCancel: () => {
12120
- agentSearchModal = null;
12121
- handle?.dispose();
12122
- draw();
12123
- },
12124
- });
12125
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12126
- handle = display.pushModal(picker.surface);
12127
- agentSearchModal = picker;
12128
- draw();
12129
- }
12130
-
12131
- /** T4-E4 — open the SSH host picker (Ctrl+K). Accept connects
12132
- * the browser pane to the remote host; Esc cancels silently.
12133
- * Routes keys through agentSearchModal (shared pattern with
12134
- * window + session pickers). */
12135
- function openSshPicker(): void {
12136
- const { cols, rows } = termSize();
12137
- const width = Math.min(72, Math.max(44, cols - 6));
12138
- const visibleRows = 8;
12139
- const height = visibleRows + 3;
12140
- const bounds = {
12141
- row: Math.max(1, Math.floor((rows - height) / 2)),
12142
- col: Math.max(1, Math.floor((cols - width) / 2)),
12143
- width,
12144
- height,
12145
- };
12146
- let handle: ReturnType<typeof display.pushModal> | null = null;
12147
- const picker = createSshPickerModal({
12148
- bounds, width, maxVisible: visibleRows,
12149
- activeHostName: workingDir.remote?.host.name ?? null,
12150
- theme: currentThemeTokens(),
12151
- onAccept: async (host) => {
12152
- agentSearchModal = null;
12153
- handle?.dispose();
12154
- touchSshHost(host.name);
12155
- startRemoteModeBridge(host, '.');
12156
- finderFeedbackRuntime.onSshConnectStarted({ name: host.name, host: host.host });
12157
- draw();
12158
- try {
12159
- await refreshRemoteWorkingDir(workingDir, {}, dockedPreview);
12160
- await refreshRemotePreviewBridge();
12161
- finderFeedbackRuntime.onSshConnectSucceeded(host.name);
12162
- } catch (err) {
12163
- finderFeedbackRuntime.onSshConnectFailed(
12164
- host.name,
12165
- err instanceof Error ? err.message : String(err),
12166
- );
12167
- }
12168
- draw();
12169
- },
12170
- onCancel: () => {
12171
- agentSearchModal = null;
12172
- handle?.dispose();
12173
- draw();
12174
- },
12175
- });
12176
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12177
- handle = display.pushModal(picker.surface);
12178
- agentSearchModal = picker;
12179
- draw();
12180
- }
12181
-
12182
11784
  // FU-1 — register ssh-picker / finder-picker openers as display
12183
11785
  // key bindings. Moves the dashboard hard branches onto the same
12184
11786
  // coordinator dispatch path the rest of the modals / bindings use.
@@ -12864,18 +12466,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
12864
12466
  submit?: (request?: string | TextInputExternalSubmitRequest) => void;
12865
12467
  getBufferText?: () => string;
12866
12468
  } = { repaint: () => {} };
12867
- repaintPromptAfterRender = () => {
12868
- if (shouldSuppressDashboardBottomArea()) {
12869
- display.setCursor(null);
12870
- return;
12871
- }
12872
- promptCtl.repaint();
12873
- };
12874
- // V3 note: repaintPromptAfterRender receives {force} from the
12875
- // coordinator hook but promptCtl.repaint() has no diff state of its
12876
- // own — the main render's force=true already did eraseDown before
12877
- // this runs, so prompt rows are clean. Forwarding force is a no-op
12878
- // today but the signature is there for future direct-paint callers.
12879
12469
 
12880
12470
  // Terminal resize wiring — CAPABILITIES-display.md §1.3 defines the
12881
12471
  // coordinator entry triplet as "readKey / mouse / resize" but the
@@ -12884,13 +12474,36 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
12884
12474
  // resize to a forced dashboard redraw plus the prompt repaint hook;
12885
12475
  // the hook is a no-op while input is idle so the call is always
12886
12476
  // safe. Dispose on process exit to keep the listener set clean.
12887
- const disposeResizeListener = installResizeListener({ draw: (opts) => draw(opts), promptCtl });
12477
+ // TUI-PICKER-RESIZE-SHRINK (#76) — the forced coordinator frame
12478
+ // re-emits open overlays (slash picker) AFTER the prompt chrome, so
12479
+ // the divider repaint cannot erase the picker's bottom border.
12480
+ const disposeResizeListener = installResizeListener({
12481
+ draw: (opts) => draw(opts),
12482
+ promptCtl,
12483
+ requestOverlayFrame: () => display.requestRender({ force: true }),
12484
+ });
12888
12485
  process.on('exit', disposeResizeListener);
12889
12486
  // 외부 주입(`pty text`)을 사용자 타이핑과 섞지 않고 별도 대기열로 — 결함 #6(10-14).
12487
+ let externalInputQueueNotice: string | null = null;
12488
+ const setExternalInputQueueNotice = (line: string | null): void => {
12489
+ const changed = updateDashboardExternalInputQueueNotice(
12490
+ chatLines,
12491
+ externalInputQueueNotice,
12492
+ line,
12493
+ (notice) => C.muted(` ${notice}`),
12494
+ );
12495
+ externalInputQueueNotice = line;
12496
+ if (changed) {
12497
+ chatScrollOffset = -1;
12498
+ draw();
12499
+ }
12500
+ };
12890
12501
  const disposeExternalInputHost = registerExternalInputHost(createDashboardExternalInputHost({
12891
12502
  isTurnActive: () => streamingInFlight,
12503
+ getTurnStop: () => currentTurnExternalStop,
12892
12504
  promptCtl,
12893
12505
  pushNotice: (line) => { chatLines.push(C.muted(` ${line}`)); chatScrollOffset = -1; draw(); },
12506
+ setQueueNotice: setExternalInputQueueNotice,
12894
12507
  }));
12895
12508
  process.on('exit', disposeExternalInputHost);
12896
12509
 
@@ -13486,6 +13099,17 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13486
13099
  try { draw(); } catch { /* TUI torn down */ }
13487
13100
  });
13488
13101
  process.once('beforeExit', () => agentDoneRedraw.dispose());
13102
+ // ⭐ TUI-AGENT-IDLE-WAKE — 배경 자식(⊕ RunShell bg) 완료 큐의 «유일한» 구독(onEnqueue 구독 0 → 1).
13103
+ // 채팅 한 줄 «✓ 위임 끝: <이름> — 다음 턴에 결과» · 결과는 다음 턴 preamble 이 싣는다.
13104
+ // ⛔ 입력칸·injectKey·pty 주입 0(대표 비침투). dedupe·수명은 모듈, 여기선 배선만.
13105
+ const agentWake = installAgentWake({
13106
+ subscribe: (listener) => globalTaskNotificationQueue.onEnqueue(listener),
13107
+ // 그리기 실패는 삼키지 않는다 — 모듈이 잡아 notified{shown:false} 로 남긴다.
13108
+ notify: createDashboardAgentWakeNotify((line) => { chatLines.push(C.muted(line)); }, () => draw()),
13109
+ // 조각 2 — 자리 TUI ∧ 유휴면 H2 문으로 다음 턴(origin child-complete) · 판정은 모듈.
13110
+ openTurn: { startTurnIfIdle: (text, opts) => internalTurnGate.startTurnIfIdle(text, opts), getCwd: () => workingDir.cwd, isPending: (taskId) => globalTaskNotificationQueue.has(taskId) },
13111
+ });
13112
+ disposeDashboardAgentWake = () => agentWake.dispose();
13489
13113
 
13490
13114
  // PLAN-chat-hud-multi-surface-port-2026-05-13 §4 M5 — 1Hz ticker
13491
13115
  // that pushes the current token-gauge + variant values onto the
@@ -15218,10 +14842,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15218
14842
  userText,
15219
14843
  hostTools: pluginHost.hostLLMTools(),
15220
14844
  runtimeTools: runtime.listToolRuntimes('tui'),
15221
- pluginTools: pluginHost.activePluginLLMTools(),
15222
14845
  optionalTools: acpTurnRef.optionalSpecs,
15223
14846
  surface: acpTurnRef.turnProfile?.surface,
15224
- rich: false,
15225
14847
  })
15226
14848
  : [];
15227
14849
  return [
@@ -15237,7 +14859,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15237
14859
  // codex-only behavioral addendum (fix L-1) inside the
15238
14860
  // universal preamble.
15239
14861
  modelFamily: getModelFamily(inspectActiveProvider().model),
15240
- rich: false,
15241
14862
  enabledTools: tools.map(t => t.name),
15242
14863
  seatBoot: true,
15243
14864
  }),
@@ -15250,10 +14871,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15250
14871
  userText,
15251
14872
  hostTools: pluginHost.hostLLMTools(),
15252
14873
  runtimeTools: runtime.listToolRuntimes('tui'),
15253
- pluginTools: pluginHost.activePluginLLMTools(),
15254
14874
  optionalTools: acpTurnRef.optionalSpecs,
15255
14875
  surface: acpTurnRef.turnProfile?.surface,
15256
- rich: false,
15257
14876
  });
15258
14877
  // ⭐ Remember this turn's catalog so a spawned sub-agent can inherit it.
15259
14878
  //
@@ -15422,8 +15041,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15422
15041
  // — the boot fell back to a fresh session. Surface the reason
15423
15042
  // so the user knows they're on a different session than asked.
15424
15043
  // - new (no resume requested): nothing extra to render.
15044
+ // 대표 #73 — 인자 없는 기동 = 새 세션. 이어받았는지는 이 값 하나로 판정한다(관측 ⊕ 자리 부트 카드).
15045
+ let tuiBootSessionStart: { origin: 'new' | 'resume'; reason: string; restoredSessionId?: string } = opts.resumeSessionId
15046
+ ? { origin: 'new', reason: 'resume-failed' }
15047
+ : { origin: 'new', reason: 'default-new' };
15425
15048
  if (dashboardAcpBootResult.mode === 'resumed' && dashboardAcpBootResult.sessionId) {
15426
15049
  const sid = dashboardAcpBootResult.sessionId;
15050
+ tuiBootSessionStart = { origin: 'resume', reason: 'resume-daemon', restoredSessionId: sid };
15427
15051
  chatLines.push(C.success(` ✓ resumed daemon session: ${sid}`));
15428
15052
  // Best-effort REST history replay over the remote HTTP/WS endpoint.
15429
15053
  if (opts.remote) {
@@ -15485,10 +15109,27 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15485
15109
  if (!resume.resumed) {
15486
15110
  chatLines.push(C.warning(` ⚠ resume failed (${resume.reason}); started new session`));
15487
15111
  } else {
15112
+ tuiBootSessionStart = {
15113
+ origin: 'resume',
15114
+ reason: resume.repaired ? 'resume-local-repaired' : 'resume-local',
15115
+ restoredSessionId: resume.sessionId,
15116
+ };
15488
15117
  chatLines.push(C.success(` ✓ resumed local session: ${resume.sessionId}`));
15118
+ if (resume.repaired) {
15119
+ chatLines.push(C.muted(` 깨진 도구 호출·결과 짝을 잘라 이어받았다(호출 ${resume.repaired.removedCalls} · 결과 ${resume.repaired.removedResults})`));
15120
+ }
15489
15121
  chatLines.push(C.muted(` ${resume.turns} turns restored. Next turn continues this conversation.`));
15490
15122
  }
15491
15123
  }
15124
+ observeSessionStart({
15125
+ surface: 'tui',
15126
+ origin: tuiBootSessionStart.origin,
15127
+ reason: tuiBootSessionStart.reason,
15128
+ // sessionId = 이 대화가 이어받은(또는 새로 연) 대화 기록의 id · acpSessionId = 백엔드 ACP 세션 id(둘이 다를 수 있다).
15129
+ sessionId: tuiBootSessionStart.restoredSessionId ?? dashboardAcpSession.currentSessionId ?? null,
15130
+ acpSessionId: dashboardAcpSession.currentSessionId ?? null,
15131
+ resumeRequested: opts.resumeSessionId !== undefined,
15132
+ });
15492
15133
  // 제안은 세션 작업 폴더 기준 — 시작 폴더 한 번이 아니라 폴더가 바뀔 때마다 다시 계산한다(IA1 리뷰 must-fix).
15493
15134
  const nextProjectSuggestion = createProjectSuggester(suggestProjectForFolder);
15494
15135
  const pushProjectSuggestion = (folder: string): void => {
@@ -15529,7 +15170,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15529
15170
  installSeatSessionRecorder({ getCwd: () => getSessionCwd(), getHistory: () => chat.history });
15530
15171
  const seatBoot = describeSeatBoot(getSessionCwd());
15531
15172
  if (seatBoot) chatLines.push(C.muted(` 🪪 ${seatBoot.seat} 자리 · 안 읽은 우편 ${seatBoot.unread ?? '미관측'} — 부트 카드를 턴마다 싣는다`));
15532
- if (dashboardAcpBootResult.mode !== 'resumed' && !opts.resumeSessionId) startSeatBootConversation({ reason: 'boot' });
15173
+ // 새 세션(이어받기 실패 포함)이면 자리 부트 카드로 시작한다 — 자리 맥락은 세션이 아니라 자리 문서가 싣는다.
15174
+ if (tuiBootSessionStart.origin === 'new') startSeatBootConversation({ reason: 'boot' });
15533
15175
  tuiSeatAskTimer = setInterval(pollTuiSeatAnswers, 10_000);
15534
15176
  tuiSeatAskTimer.unref();
15535
15177
  pollTuiSeatAnswers();
@@ -16587,7 +16229,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16587
16229
  // 다음 inner-loop 반복이 남은 항목도 같은 순서로 제출한다.
16588
16230
  // ⭐ 드레인 순서 자체는 `chat/turn-typeahead.ts` 가 소유한다 — 여기서 베끼면
16589
16231
  // 테스트가 그 순서를 다시 베껴야 하고, 베낀 테스트는 배선 회귀를 못 잡는다.
16590
- const interrupted = lastTurnFinalStatus === 'interrupted';
16232
+ internalTurnGate.noteTurnSettled();
16233
+ // «지금 밀어 넣기»로 끊은 턴은 «중단»이 아니다 — 복원하지 않고 큐 머리를 곧바로 제출한다.
16234
+ const interrupted = consumeTurnEndInterrupted(lastTurnFinalStatus, steerInterruptRef);
16591
16235
  const queuedBeforeDrain = turnTypeaheadRef.state.queuedSubmissions.length;
16592
16236
  // ⭐ 드레인 → 레지스트리 해제 → Enter 주입은 다리 모듈이 한 함수로 한다(시험이 같은 함수를 문다).
16593
16237
  const drained = handoffTurnTypeaheadAtTurnEnd(turnTypeaheadRef, nextInitial, { interrupted }, () => {
@@ -16619,6 +16263,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16619
16263
  // C-d-1 — 하단 슬롯 결정 뷰 활성 시 composer 페인트 정지(단일 소유자).
16620
16264
  shouldPaint: () => !shouldSuppressDashboardBottomArea() && !bottomSlotModalActive(),
16621
16265
  promptCtl,
16266
+ promptRowOwner,
16622
16267
  setInputLines: (n) => dashboardState.setInputLines(n),
16623
16268
  redraw: draw,
16624
16269
  dispatchGlobalAction: runChatMainGlobalAction,
@@ -16632,6 +16277,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16632
16277
  : '/quit /clear (run `elanous setup` — no LLM provider available)',
16633
16278
  debugLog: debug.enabled ? (event, label, payload) => debug.log(event, label, payload) : undefined,
16634
16279
  textInputOpts: {
16280
+ internalTurnYield: internalTurnYieldPort,
16635
16281
  onScrollKey: (name) => {
16636
16282
  const { rows } = termSize();
16637
16283
  const viewport = computePromptFrameLogViewportBounds(
@@ -17073,6 +16719,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17073
16719
  }),
17074
16720
  },
17075
16721
  });
16722
+ // ⭐ H2 — 입력칸이 내부 턴 문에 «비켜 줬다». 꺼낸 요청을 같은 턴 실행 경로로 보낸다
16723
+ // (슬래시 해석·히스토리·사람 발화 에코 없음). 꺼낼 게 없으면 입력으로 되돌아간다.
16724
+ const consumed = consumeInternalTurnYield(internalTurnGate, input);
16725
+ if (consumed.kind === 'reenter') continue;
16726
+ const internalTurn: InternalTurnRequest | null = consumed.kind === 'internal' ? consumed.request : null;
16727
+ input = consumed.input;
17076
16728
 
17077
16729
  const postTurn = resolveDashboardChatMainPostTurn(input, {
17078
16730
  inputLines: dashboardState.getInputLines(),
@@ -17124,7 +16776,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17124
16776
 
17125
16777
  // Save to durable input history. Up/down reads the in-memory
17126
16778
  // tail, while slash/LLM tools can search the full store.
17127
- if (postTurn.historyRecord) {
16779
+ if (!internalTurn) internalTurnGate.noteHumanTurn();
16780
+ if (postTurn.historyRecord && !internalTurn) {
17128
16781
  inputHistoryStore.record(postTurn.historyRecord);
17129
16782
  refreshInputHistory();
17130
16783
  }
@@ -17282,14 +16935,16 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17282
16935
  // resource_link blocks so dropped screenshots and PDFs flow
17283
16936
  // naturally to the agent. debug.log gates the snapshot so
17284
16937
  // off-state stays zero-cost.
17285
- const submitIntent = resolveDashboardHarnessSubmitIntent(input.text, {
17286
- stickyBackend: dashboardAcpChat.getSticky(),
17287
- source: input.externalSubmitSource,
17288
- });
16938
+ const submitIntent = internalTurn
16939
+ ? resolveInternalTurnSubmitIntent(internalTurn, dashboardAcpSession.currentSessionId)
16940
+ : resolveDashboardHarnessSubmitIntent(input.text, {
16941
+ stickyBackend: dashboardAcpChat.getSticky(),
16942
+ source: input.externalSubmitSource,
16943
+ });
17289
16944
  const cmdText = submitIntent.kind === 'control-turn'
17290
16945
  ? (submitIntent.commandText ?? '')
17291
16946
  : submitIntent.text;
17292
- if (submitIntent.kind === 'submit-turn' && parseSeatAsk(submitIntent.text)) {
16947
+ if (submitIntent.kind === 'submit-turn' && !internalTurn && parseSeatAsk(submitIntent.text)) {
17293
16948
  try {
17294
16949
  const receipt = await askSeat(submitIntent.text, { channel: 'tui', clientId: tuiSeatAskClientId },
17295
16950
  ceoTaskDeps(getUserConfig(), null), tuiSeatAskDeps);
@@ -17959,6 +17614,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17959
17614
  ? { url: opts.remote.url, token: opts.remote.token }
17960
17615
  : null,
17961
17616
  acpSwapTo: (sessionId, cwd) => dashboardAcpSession.swapTo(sessionId, cwd),
17617
+ acpRenew: async (cwd) => {
17618
+ const renewed = await dashboardAcpSession.renew(cwd);
17619
+ acpSessionIdRef.current = renewed;
17620
+ return renewed;
17621
+ },
17962
17622
  },
17963
17623
  shellSlash: {
17964
17624
  slashRuntime: shellSlashRuntime,
@@ -18021,6 +17681,21 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18021
17681
  setWorkingFocusPreview: () => { setWorkingFocus('preview', 'slash-term-move-preview'); },
18022
17682
  nextTerminalVwSlotId: (terminalId) => `term-slot:${terminalId}`,
18023
17683
  },
17684
+ // TUI-LOOP-COMMAND — /loop <간격> <프롬프트> · 쉬는 동안만 발사
17685
+ // (src/dashboard/loop-scheduler.ts · 판정: idle 발사 1 · busy 미룸 · stop 0).
17686
+ // idle/busy 신호는 대시보드 루프가 이미 쓰는 것을 그대로 쓴다 —
17687
+ // 스트리밍 턴(streamingInFlight · reportTuiTurn busy/idle) +
17688
+ // ACP 세션 바쁨(dashboardAcpChat.isBusy()). 프롬프트 제출은
17689
+ // 플러그인 submitDashboardText 와 같은 기존 제출 틈
17690
+ // (dispatchDashboardSubmitText)으로 낸다 — 새 제출 경로를 만들지 않는다.
17691
+ loopCommand: {
17692
+ isIdle: () => !streamingInFlight && !dashboardAcpChat.isBusy(),
17693
+ submitPrompt: (prompt: string) => {
17694
+ if (dispatchDashboardSubmitText) dispatchDashboardSubmitText(prompt);
17695
+ else chatLines.push(C.muted(' /loop: 입력 루프가 아직 제출을 받지 않는다 — 다음 틱을 기다린다'));
17696
+ },
17697
+ schedulerRef: dashboardLoopSchedulerRef,
17698
+ },
18024
17699
  };
18025
17700
  const slashOutcome = await dashboardSlashRegistry.dispatch(cmdLower, args, slashCtx);
18026
17701
  if (debug.enabled) {
@@ -18468,7 +18143,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18468
18143
  // pattern. Without this draw(), the echo sits in the chatLines
18469
18144
  // array but doesn't paint until after the skill router's
18470
18145
  // classifier LLM call returns (~1-2s of visual silence).
18471
- echoSubmittedUserText(tok.text);
18146
+ if (internalTurn) {
18147
+ chatLines.push(C.muted(` ${formatInternalTurnBanner(internalTurn)}`));
18148
+ chatScrollOffset = -1;
18149
+ } else {
18150
+ echoSubmittedUserText(tok.text);
18151
+ }
18472
18152
  if (queuedTurnEndText !== undefined && input.text === queuedTurnEndText) {
18473
18153
  debug.log('dashboard.turn-typeahead', 'echoed', { path: 'turn-end', length: queuedTurnEndText.length });
18474
18154
  }
@@ -18686,7 +18366,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18686
18366
  let latestTurnUsage: import('../prompt-cache/types.js').LLMUsage | undefined;
18687
18367
  const runPlainSubmitTurn = (submit: PlainTurnSubmit) => runDashboardChatMainPlainTurn({
18688
18368
  // ⛔⭐ 관측 심을 «실제로» 넘긴다 — 타입만 선택적으로 넓히면 조용히 안 온다(`F12`).
18689
- debugLog: (category, event, payload) => { try { debug.log(category, event, payload); } catch { /* fail-soft */ } },
18369
+ debugLog: (category, event, payload, opts) => { try { debug.log(category, event, payload, opts); } catch { /* fail-soft */ } },
18690
18370
  userText: tok.text,
18691
18371
  intent: submit.intent,
18692
18372
  contextText: ctx,
@@ -18759,10 +18439,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18759
18439
  userText: args.userText,
18760
18440
  hostTools: pluginHost.hostLLMTools(),
18761
18441
  runtimeTools: runtime.listToolRuntimes('tui'),
18762
- pluginTools: pluginHost.activePluginLLMTools(),
18763
18442
  optionalTools: acpTurnRef.optionalSpecs,
18764
18443
  surface: acpTurnRef.turnProfile?.surface,
18765
- rich: false,
18766
18444
  })
18767
18445
  : [];
18768
18446
  return [
@@ -18772,7 +18450,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18772
18450
  cwd: args.cwd,
18773
18451
  turnProfile: args.turnProfile as SessionTurnProfile,
18774
18452
  userConfig: args.userConfig,
18775
- rich: false,
18776
18453
  enabledTools: tools.map(t => t.name),
18777
18454
  seatBoot: true,
18778
18455
  }),
@@ -18897,6 +18574,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18897
18574
  },
18898
18575
  runTurnUsageRuntime: (usage) => {
18899
18576
  latestTurnUsage = usage as import('../prompt-cache/types.js').LLMUsage;
18577
+ // 이번 턴의 «실제» 공급자·모델(폴백이면 넘어간 쪽) — 완료 배지가 «A → B» 로 보인다(TUI-MODEL-BADGE-ACTUAL).
18578
+ // 원격 데몬 세션은 적지 않는다(결정이 데몬 쪽 · 후속 칸 TUI-MODEL-BADGE-REMOTE).
18579
+ recordDashboardTurnExecution(usage as { model?: string; routeProvider?: string }, { remote: daemonAttached });
18900
18580
  runDashboardTurnUsageRuntime({
18901
18581
  // The plain-turn pipeline threads usage as `unknown` for
18902
18582
  // decoupling; at this consumer it is the concrete LLM usage
@@ -19046,7 +18726,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19046
18726
  const { resumeGoalFromUserFollowUp } = await import('../goals/chat-loop-bridge.js');
19047
18727
  const { hydrateGoalFromSnapshot } = await import('../goals/registry.js');
19048
18728
  hydrateGoalFromSnapshot();
19049
- const followUpAction = tok.text.startsWith('/goal')
18729
+ // ⛔ H2 — 내부 턴은 사람의 «정정·재개» 발화가 아니다: 멈춘 /goal 을 되살리지 않는다.
18730
+ const followUpAction = tok.text.startsWith('/goal') || internalTurn
19050
18731
  ? { kind: 'no-loop' as const }
19051
18732
  : resumeGoalFromUserFollowUp(tok.text);
19052
18733
  let goalLoopUserText: string | null = followUpAction.kind === 'continue'
@@ -19233,5 +18914,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19233
18914
  } finally {
19234
18915
  setSelfImplementSoftStopSpaceObserver(null);
19235
18916
  if (tuiSeatAskTimer) clearInterval(tuiSeatAskTimer);
18917
+ wakeupScheduler.dispose();
19236
18918
  }
19237
18919
  }