elanous 0.2.28 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (818) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{y1ltt7oCZxVpC6zVPhZ2O → LSXfY0u9QtfDbejY0Ukf_}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{ab4d57c2.e90bc2cb31949045.js → 0bb0ff36.b8d418980951e5ba.js} +7 -7
  5. package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +27 -0
  8. package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +287 -0
  9. package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +62 -0
  10. package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{5492-e05e26a033ba80d2.js → 1335-6731f392c35216eb.js} +3 -3
  12. package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1654.32de4b324981a188.js +23 -0
  15. package/apps/pwa/out/_next/static/chunks/1834-50625904996a8fa8.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +149 -0
  17. package/apps/pwa/out/_next/static/chunks/1913.7cc6fb6055b1ed3f.js +41 -0
  18. package/apps/pwa/out/_next/static/chunks/2001.6c7e0fd02190886c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2112-e4efc21a6505d5bc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{8578.dff22d988cc2b106.js → 2213.2ccbada92c33ea0f.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2534.e85ae21ad0dac829.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{5081.a240b63fad28663a.js → 2871.0bca6137343b3f76.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/2898.fd781bc7cf01a7b7.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2940-619bfd8ef337c611.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/{2846.d6a433b8663819a5.js → 2985.39ccdecd36848b12.js} +4 -4
  31. package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +115 -0
  32. package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +64 -0
  33. package/apps/pwa/out/_next/static/chunks/{f4dfbfc0.a69fb12f854882aa.js → 3267c0e6.d34854e7b3984a6e.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/3278.2e5d15b53a4091c8.js +2 -0
  35. package/apps/pwa/out/_next/static/chunks/3541-ce3a5424ed5ad7f1.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3583.a05e88cc8d594628.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{3143.257997a5f1ed79df.js → 3902.e1e71aaac850a045.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{16e57f67.3ea19e893461e611.js → 409f1e57.d0649fc47691ebfe.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/{1146.80d15ea7c6e6ee95.js → 4111.2bfedbb233ad206b.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{1516.83dd2c1b86b4e507.js → 4174.92ed04922709ac9e.js} +2 -2
  43. package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +36 -0
  45. package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +286 -0
  49. package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/4745.67d6f99c245af3f7.js +226 -0
  51. package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/{9938.b23e4d4dfa0114cd.js → 4999.c064a4000c2a9e94.js} +2 -2
  53. package/apps/pwa/out/_next/static/chunks/5173-233188df2f42c626.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/{1157.f08bf2b70915e176.js → 5304.04d24756f242a094.js} +2 -2
  57. package/apps/pwa/out/_next/static/chunks/{7752.5cbdd0b433c54af9.js → 5307.60f2209e809bd627.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/{9917-9e26abc5f7772735.js → 5458-16cd7a2b51b74c8c.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/{6196.34d6b229079e9e28.js → 5525.86debf231e181813.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{4593.10f5b9bd702d4a6c.js → 5543.a438b8ab5a13ea47.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +74 -0
  65. package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +94 -0
  67. package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{7483.ba01c6c209b81bba.js → 6092.a7f99e68b188ca66.js} +1 -1
  70. package/apps/pwa/out/_next/static/chunks/60fb96c9.360b01d8d4927b7b.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/{9974.e77b4ff4884557b3.js → 6157.0c9af021de4fb31b.js} +2 -2
  72. package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/{3309.364573ceedbdfd5f.js → 6323.8672b4817a306928.js} +1 -1
  74. package/apps/pwa/out/_next/static/chunks/{94.4cfed21c0b781a06.js → 6433.fc573af22bbc9ab7.js} +4 -4
  75. package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/{949.1e7d45ccb71580aa.js → 6517.4147878ac06d584d.js} +2 -2
  77. package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +32 -0
  78. package/apps/pwa/out/_next/static/chunks/6544cb48.a87fe20ddf137be2.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +104 -0
  80. package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{2528.3868070ef1be69f9.js → 6767.ea607cf0c20a25af.js} +2 -2
  85. package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +93 -0
  86. package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{1743.563d3168e95825ef.js → 7502.ccd5b8065545edd0.js} +2 -2
  93. package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +8 -0
  94. package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{5408.377c8c543635b81f.js → 7862.30a8d6482bb238eb.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{65ebff95.081daeff622d238e.js → 7a1887c4.f36b88fba31da8fd.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{2697.76ca2ac3efef6f3b.js → 8018.a32663f04a6fc5f9.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9002.e5658224e3dc1b6f.js +24 -0
  109. package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9138.b6e80031afb1d973.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/{4204.a409ab6250c0f6a8.js → 9221.7d22eaf353a2d4e7.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/{5785-ea8d2c633b6688e3.js → 9279-d28c5b7754d010ae.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9398-6aed28f0a96d0d6f.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9407.e49124dedf0f5e72.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/997ce8a5-2ad501927003b41b.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{9311.24c8f5ea793253dd.js → 9989.3694300e5f000b14.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-3535a4a69f1fafaa.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-fa46d4588e8442c2.js → page-2ebb8fd59b9461f9.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-5a503541353e60a8.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-6025101384e361a5.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-a645f00a06483e9a.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e9bab7413b028b28.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-e547e6ce55f1fb33.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-d27e1b9850fd54bf.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-0fec29c7ba01894b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-339a07410cd6f36b.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/{page-ced0bcfc6542b3fc.js → page-349f2752d8cd37ad.js} +1 -1
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-2fd93ed8c9f16580.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-9b564dbff0fefc35.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-814ba51385225776.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-397d479626e5a459.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-af1bba4e3251b728.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3ad3be5e7d3fdce9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4da93ad35d4a8d28.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-3a658d5fa6d16a25.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-0157de6e85f8bb6b.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-cb249e81ed339318.js → page-0a07363b130182a1.js} +1 -1
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-841d73d4ac44ed49.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6f43a3d1146e6377.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-521ee23ac8c0abfa.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-a9992ee285efaa83.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-3d48e85fa89ec04f.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-233c71cd5f68f0d7.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-70151d78de2f375e.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-dcac33a3e2cb72c0.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0448145ccd5c45c1.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{6def8a8e.c81f9ca41028eccf.js → b313b242.cb1265eea908db61.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{e87d89b9.2e71c0ae475d28a1.js → e5012ffe.f727e23c231f4342.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/{e117f801.e92bf10242653497.js → ebc7d829.264a0bcb6c750b50.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +1 -0
  183. package/apps/pwa/out/_next/static/css/{72becfd42807b50d.css → 3f5fc6f96e2cf7b2.css} +1 -1
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +23 -23
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +23 -23
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +23 -23
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +22 -22
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +22 -22
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +23 -23
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +22 -22
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +23 -23
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +23 -23
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +22 -22
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +23 -23
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +23 -23
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +23 -23
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +23 -23
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +22 -22
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +22 -22
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +23 -23
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +23 -23
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +23 -23
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +22 -22
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +22 -22
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +23 -23
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +23 -23
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +22 -22
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +22 -22
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +22 -22
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +23 -23
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +23 -23
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +22 -22
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +23 -23
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +23 -23
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +23 -23
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +23 -23
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +23 -23
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +22 -22
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +23 -23
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +23 -23
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +23 -23
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +23 -23
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +23 -23
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +23 -23
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +23 -23
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +23 -23
  277. package/bin/elanous.mjs +7 -3
  278. package/graphs/context/nightly-condense.yaml +1 -0
  279. package/graphs/intake/intake-daily.yaml +8 -1
  280. package/graphs/intake/recipes.yaml +5 -0
  281. package/graphs/lecture/lecture-note.yaml +2 -2
  282. package/graphs/ops/harness-gc.yaml +1 -0
  283. package/graphs/release/recipes.yaml +3 -0
  284. package/graphs/release/release-loop.yaml +15 -7
  285. package/graphs/units/recipes.yaml +3 -0
  286. package/graphs/units/release-gate-recheck.yaml +15 -0
  287. package/package.json +2 -1
  288. package/plugins/agent-team/agents/research.md +1 -0
  289. package/scripts/check-native-tool-wiring.ts +3 -2
  290. package/scripts/ci-android-unit-tests.ts +16 -0
  291. package/scripts/ci-typecheck-changed.ts +40 -11
  292. package/scripts/coord-channel-watch.sh +45 -19
  293. package/scripts/coord-role-pack.ts +222 -0
  294. package/scripts/cron-run.ts +96 -3
  295. package/scripts/detect-test-interference.ts +38 -5
  296. package/scripts/gate-pr-cache.ts +65 -0
  297. package/scripts/gh-surface/capture.ts +126 -0
  298. package/scripts/gh-surface/gh +40 -0
  299. package/scripts/import-graph-scc.ts +38 -0
  300. package/scripts/install.ps1 +19 -0
  301. package/scripts/intake-social.ts +181 -0
  302. package/scripts/lecture-note/node.ts +71 -4
  303. package/scripts/lib/android-toolchain.ts +32 -0
  304. package/scripts/lib/deterministic-env.ts +62 -0
  305. package/scripts/lib/gate-test-env.ts +19 -0
  306. package/scripts/lib/morning-report-options.ts +41 -0
  307. package/scripts/measure-goal-grounding-corpus.ts +2 -1
  308. package/scripts/pr-land-extra-remote-gates.ts +49 -0
  309. package/scripts/ratchets/test-interference-patterns.allowlist.txt +257 -0
  310. package/scripts/ratchets/test-interference-patterns.ts +367 -0
  311. package/scripts/release-loop/cut-branch.ts +4 -3
  312. package/scripts/release-loop/export-check-node.ts +0 -1
  313. package/scripts/release-loop/fixtures/warm-failure-assertion/sample.junit.xml +8 -0
  314. package/scripts/release-loop/fixtures/warm-job-029/sample.json +1 -0
  315. package/scripts/release-loop/fixtures/warm-job-029/sample.junit.xml +10 -0
  316. package/scripts/release-loop/gate-batch-safe.ts +23 -0
  317. package/scripts/release-loop/gate-closure.ts +12 -8
  318. package/scripts/release-loop/gate-diff.ts +1 -1
  319. package/scripts/release-loop/gate-history.ts +422 -0
  320. package/scripts/release-loop/gate-host-mirror.ts +66 -0
  321. package/scripts/release-loop/gate-node.ts +806 -94
  322. package/scripts/release-loop/gate-partial.ts +106 -9
  323. package/scripts/release-loop/gate-pr-cache.ts +66 -0
  324. package/scripts/release-loop/gate-prewarm-plan.ts +66 -0
  325. package/scripts/release-loop/gate-r2-verdict.ts +189 -0
  326. package/scripts/release-loop/gate-recheck-plan-node.ts +52 -0
  327. package/scripts/release-loop/gate-triage-node.ts +190 -0
  328. package/scripts/release-loop/gate-warm-artifacts.ts +253 -0
  329. package/scripts/release-loop/import-graph-scc-run.ts +54 -0
  330. package/scripts/release-loop/import-graph-scc.ts +215 -0
  331. package/scripts/release-loop/node-verdict.ts +2 -0
  332. package/scripts/release-loop/probe-pod-host-dirs-live.ts +113 -0
  333. package/scripts/release-loop/resume-release.ts +33 -4
  334. package/scripts/release-loop/resume.ts +13 -5
  335. package/scripts/release-loop/spawn-lane.ts +51 -0
  336. package/scripts/release-loop/tui-node-checks.ts +23 -0
  337. package/scripts/release-loop/tui-sim-node.ts +3 -2
  338. package/scripts/release-loop/unattended-release.ts +16 -0
  339. package/scripts/release-loop/version-node.ts +62 -4
  340. package/scripts/test-deterministic.ts +46 -73
  341. package/scripts/train-reach-grouping.ts +48 -0
  342. package/scripts/tsc-incremental-cache.ts +86 -0
  343. package/scripts/tsc-incremental-diagnostics.ts +76 -0
  344. package/scripts/tsc-main-safety-net.ts +2 -2
  345. package/scripts/usage-rollup.ts +11 -3
  346. package/src/acp/client.ts +4 -1
  347. package/src/acp/core-turn-bridge.ts +145 -24
  348. package/src/acp/elanous-extensions.ts +2 -0
  349. package/src/acp/server.ts +2 -0
  350. package/src/ad-pipeline/run.ts +4 -2
  351. package/src/agent/ref-grounding.ts +18 -0
  352. package/src/agent/runner.ts +169 -3
  353. package/src/agent/subagent-callable.ts +3 -2
  354. package/src/agent-mission/review-loop.ts +6 -3
  355. package/src/agent-substrate/acp-implementer.ts +14 -2
  356. package/src/agent-substrate/pr-reviewer.ts +41 -21
  357. package/src/agent-substrate/self-review-cli.ts +30 -4
  358. package/src/autopilot/pr-manager.ts +62 -7
  359. package/src/briefing/brief-items.ts +24 -22
  360. package/src/budget/pty-usage-reemit.ts +7 -0
  361. package/src/budget/run-usage-rollup.ts +9 -0
  362. package/src/budget/usage-daily-store.ts +98 -0
  363. package/src/capture/tui-self-report.ts +37 -4
  364. package/src/chat/chat-cli.ts +10 -2
  365. package/src/chat/index.ts +107 -20
  366. package/src/chat/input-control-key.ts +5 -3
  367. package/src/chat/pickers/modal-runtime.ts +1 -0
  368. package/src/chat/pickers/modals.ts +3 -1
  369. package/src/chat/tool-interrupt-behavior.ts +38 -0
  370. package/src/chat/tool-render/dispatch.ts +5 -1
  371. package/src/chat/turn-typeahead.ts +191 -29
  372. package/src/cli/__fixtures__/agent-help-before/agent.txt +4 -2
  373. package/src/cli/agent-cli.ts +41 -13
  374. package/src/cli/config-audit.ts +123 -0
  375. package/src/cli/dashboard-name-flag.ts +48 -0
  376. package/src/cli/decisions-cli.ts +80 -6
  377. package/src/cli/doctor-cli.ts +80 -0
  378. package/src/cli/log-event-names.ts +19 -0
  379. package/src/cli/mcp-login.ts +21 -5
  380. package/src/cli/msg-cli.ts +133 -2
  381. package/src/cli/pr-cli.ts +546 -112
  382. package/src/cli/pr-land-heavy-remote.ts +233 -0
  383. package/src/cli/pr-land-overlap-evidence.ts +167 -14
  384. package/src/cli/pr-land-pod-shadow-job.ts +83 -0
  385. package/src/cli/pr-land-pod-shadow.ts +16 -3
  386. package/src/cli/pr-land-virtual-rebase.ts +108 -0
  387. package/src/cli/pty-takeover-cli.ts +125 -16
  388. package/src/cli/release-cli.ts +122 -14
  389. package/src/cli/remote-resolve.ts +2 -1
  390. package/src/cli/seat-cli.ts +164 -19
  391. package/src/cli/self-update.ts +5 -2
  392. package/src/cli/tasks-cli.ts +105 -21
  393. package/src/cli/usage-cli.ts +146 -12
  394. package/src/cli/usage-daily-cli.ts +84 -0
  395. package/src/context-bus/context-first.ts +13 -0
  396. package/src/context-bus/context-now.ts +95 -7
  397. package/src/context-bus/ops-events.ts +75 -0
  398. package/src/control-plane/lease-holder.ts +12 -8
  399. package/src/control-plane/ledger.ts +163 -4
  400. package/src/control-plane/server.ts +91 -11
  401. package/src/core-turn/auth-rejected-notice.ts +41 -0
  402. package/src/core-turn/run-core-turn.ts +14 -2
  403. package/src/core-turn/run-goal-loop.ts +239 -4
  404. package/src/core-turn/seat-turn-cap.ts +79 -0
  405. package/src/core-turn/short-question.ts +78 -13
  406. package/src/core-turn/types.ts +10 -0
  407. package/src/dashboard/acp-active-model.ts +40 -0
  408. package/src/dashboard/acp-boot.ts +3 -0
  409. package/src/dashboard/auto-compact-runtime.ts +2 -0
  410. package/src/dashboard/boot-banner.ts +80 -0
  411. package/src/dashboard/dashboard-status-lines.ts +11 -0
  412. package/src/dashboard/detail-viewer-runtime.ts +15 -2
  413. package/src/dashboard/event-loop-stall-monitor.ts +4 -0
  414. package/src/dashboard/goal-loop-thinking.ts +16 -0
  415. package/src/dashboard/hitl-boot.ts +9 -4
  416. package/src/dashboard/index.ts +448 -998
  417. package/src/dashboard/input/chat-main-entry-runtime.ts +2 -0
  418. package/src/dashboard/input/chat-main-log-actions.ts +4 -0
  419. package/src/dashboard/input/chat-main-log-command.ts +12 -1
  420. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +176 -4
  421. package/src/dashboard/input/chat-main-session.ts +14 -3
  422. package/src/dashboard/input/chat-main-slash-command.ts +7 -0
  423. package/src/dashboard/input/chat-main-turn.ts +14 -4
  424. package/src/dashboard/input/compact-prepass-recovery.ts +130 -0
  425. package/src/dashboard/input/dashboard-chord-actions.ts +10 -1
  426. package/src/dashboard/input/dashboard-chord-runtime.ts +2 -0
  427. package/src/dashboard/input/external-input-host.ts +71 -0
  428. package/src/dashboard/input/internal-turn.ts +261 -0
  429. package/src/dashboard/input/log-fold-binding.ts +18 -0
  430. package/src/dashboard/input/mouse-wiring.ts +17 -4
  431. package/src/dashboard/input/seat-autowake.ts +318 -0
  432. package/src/dashboard/input/turn-steer-bridge.ts +164 -0
  433. package/src/dashboard/log-click-runtime.ts +5 -3
  434. package/src/dashboard/loop-scheduler.ts +72 -0
  435. package/src/dashboard/running-build-status.ts +74 -0
  436. package/src/dashboard/seat-boot-preamble.ts +131 -0
  437. package/src/dashboard/seat-local-instructions.ts +305 -0
  438. package/src/dashboard/skill-runtime.ts +2 -0
  439. package/src/dashboard/slash-runtime/dashboard-handlers.ts +220 -1203
  440. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -1
  441. package/src/dashboard/slash-runtime/index.ts +13 -0
  442. package/src/dashboard/slash-runtime/model-choices.ts +35 -0
  443. package/src/dashboard/term-slash-runtime.ts +15 -0
  444. package/src/dashboard/turn-busy-teardown.ts +50 -0
  445. package/src/dashboard/turn-preamble.ts +30 -2
  446. package/src/dashboard/turn-session-layers.ts +176 -0
  447. package/src/dashboard/turn-stream-formatter.ts +14 -6
  448. package/src/dashboard/turn-stream-presentation-applier.ts +8 -0
  449. package/src/decisions/decision-ledger.ts +45 -4
  450. package/src/discord-self-message.ts +4 -4
  451. package/src/domains/release-tool.ts +26 -10
  452. package/src/domains/surface-events.ts +4 -1
  453. package/src/domains/watch-default-scheduler.ts +8 -9
  454. package/src/field-note/transcribe.ts +17 -0
  455. package/src/fold-stack.ts +24 -1
  456. package/src/git-fs/dirty.ts +32 -4
  457. package/src/git-fs/index.ts +43 -1
  458. package/src/github/remote-call.ts +81 -0
  459. package/src/goals/registry.ts +46 -6
  460. package/src/goals/types.ts +7 -1
  461. package/src/graph-runner/runner.ts +2 -0
  462. package/src/grok/credential.ts +26 -0
  463. package/src/harness/harness-clean.ts +2 -1
  464. package/src/harness/harness-cli-command.ts +52 -5
  465. package/src/harness/harness-gc.ts +34 -6
  466. package/src/harness/harness-incidents.ts +57 -20
  467. package/src/harness/harness-pod-dispatch.ts +87 -10
  468. package/src/harness/harness-queue.ts +53 -4
  469. package/src/harness/harness-stop.ts +4 -3
  470. package/src/harness/harness-substrate-default.ts +78 -7
  471. package/src/harness/harness-worktree-auto.ts +2 -1
  472. package/src/harness/harness-worktrees.ts +3 -2
  473. package/src/hooks/dispatch.ts +2 -1
  474. package/src/index.ts +166 -26
  475. package/src/input/external-input-queue.ts +251 -0
  476. package/src/intake-plane/slash.ts +14 -3
  477. package/src/interactive-terminal-modal.ts +13 -0
  478. package/src/keybindings.ts +4 -1
  479. package/src/llm/route-decision.ts +25 -0
  480. package/src/llm/tool-loop-steered-rejection.ts +33 -0
  481. package/src/llm-stream-retry-display.ts +59 -0
  482. package/src/llm.ts +391 -74
  483. package/src/log-entry.ts +3 -3
  484. package/src/log-pane/search.ts +29 -4
  485. package/src/loops/agent-reply.ts +256 -0
  486. package/src/loops/budget.ts +37 -7
  487. package/src/loops/consensus.ts +348 -0
  488. package/src/loops/loop-cli.ts +3 -2
  489. package/src/loops/msg-address.ts +57 -0
  490. package/src/loops/msg-wake.ts +484 -0
  491. package/src/loops/observe.ts +19 -3
  492. package/src/loops/orchestrator/inbox.ts +60 -3
  493. package/src/loops/orchestrator/tick.ts +80 -6
  494. package/src/loops/orchestrator/traffic.ts +64 -1
  495. package/src/loops/registry.ts +72 -4
  496. package/src/market/signed-index.ts +14 -0
  497. package/src/mcp/mcp-oauth.ts +54 -5
  498. package/src/memory.ts +20 -5
  499. package/src/models/prompts.ts +28 -0
  500. package/src/msg/msg-sse-client.ts +223 -0
  501. package/src/msg/msg-store.ts +86 -2
  502. package/src/mss/logging/log-store.ts +225 -1
  503. package/src/native-tool-catalog.ts +125 -0
  504. package/src/nexus/DAEMON-THIN-inventory-2026-10-15.md +122 -0
  505. package/src/nexus/api/http-server.ts +59 -15
  506. package/src/nexus/api/loop-resources.ts +19 -3
  507. package/src/nexus/api/ops-api.ts +13 -0
  508. package/src/nexus/api/project-autopsy.ts +127 -0
  509. package/src/nexus/api/terminals.ts +3 -0
  510. package/src/nexus/api/workflow-approvals.ts +2 -0
  511. package/src/nexus/daemon-thin-inventory.md +37 -0
  512. package/src/nexus/index.ts +70 -0
  513. package/src/nexus/onboarding/first-boot-wizard.ts +56 -2
  514. package/src/nexus/state/state.ts +1 -0
  515. package/src/oauth/codex-account-store.ts +15 -3
  516. package/src/oauth/codex-billing-route.ts +31 -0
  517. package/src/oauth/store.ts +32 -7
  518. package/src/onboarding/first-run.ts +28 -3
  519. package/src/ops/overview-cache.ts +16 -4
  520. package/src/ops/overview-collect.ts +19 -2
  521. package/src/ops/resources.ts +82 -0
  522. package/src/panes/log-pane.ts +67 -8
  523. package/src/persona/presets.ts +44 -0
  524. package/src/plugins/maker/connector-maker.ts +41 -0
  525. package/src/plugins/maker/skill-maker.ts +42 -0
  526. package/src/plugins/maker/wizard-generate.ts +6 -4
  527. package/src/pr-tool/census.ts +288 -0
  528. package/src/preview/terminal.ts +14 -1
  529. package/src/prompt-cache/types.ts +2 -0
  530. package/src/prompt-enhance/enhance.ts +24 -8
  531. package/src/prompt-library/chat-conciseness.ts +13 -0
  532. package/src/prompt-library/universal-preamble.ts +283 -61
  533. package/src/pty-shell/pty-control-ipc.ts +4 -1
  534. package/src/pty-shell/pty-manifest.ts +77 -5
  535. package/src/pty-shell/registry.ts +19 -2
  536. package/src/pty-shell/tui-run-state.ts +253 -0
  537. package/src/release-loop/gate-host-reservation.ts +97 -0
  538. package/src/release-loop/landing-freeze.ts +5 -0
  539. package/src/release-loop/light-rc.ts +32 -4
  540. package/src/release-loop/placement.ts +92 -1
  541. package/src/release-loop/rehearsal-shard-plan.ts +149 -0
  542. package/src/release-loop/rehearsal.ts +73 -17
  543. package/src/render.ts +5 -1
  544. package/src/repl/index.ts +20 -2
  545. package/src/seat-loop/seat-knowhow.ts +282 -0
  546. package/src/seat-loop/seat-loop.ts +41 -2
  547. package/src/seat-loop/seat-report.ts +42 -2
  548. package/src/seat-loop/traffic-signal.ts +3 -0
  549. package/src/self-dev/ask-launch-flow.ts +24 -3
  550. package/src/self-dev/decompose-compose-graph.ts +28 -27
  551. package/src/self-dev/decompose-proposal.ts +53 -38
  552. package/src/self-dev/decompose.ts +17 -0
  553. package/src/self-dev/dev-pipeline.ts +31 -2
  554. package/src/self-dev/draft-residue.ts +24 -9
  555. package/src/self-dev/draft-sweep.ts +59 -4
  556. package/src/self-dev/external-call-zero.ts +77 -0
  557. package/src/self-dev/launch-preflight-text.ts +6 -0
  558. package/src/self-dev/launch-preflight.ts +25 -3
  559. package/src/self-dev/local-supervisor-judge.ts +185 -0
  560. package/src/self-dev/run-supervisor.ts +300 -24
  561. package/src/self-dev/run-types.ts +20 -0
  562. package/src/self-dev/shard-aggregate.ts +15 -2
  563. package/src/self-dev/shard-plan.ts +47 -0
  564. package/src/self-dev/shard-run.ts +135 -12
  565. package/src/self-dev/spawn-lane-files.ts +19 -0
  566. package/src/self-health/signatures.ts +132 -0
  567. package/src/self-implement/app-api-budget.ts +51 -11
  568. package/src/self-implement/config-gate.ts +2 -1
  569. package/src/self-implement/doctor-visit.ts +118 -0
  570. package/src/self-implement/expose-gate.ts +2 -1
  571. package/src/self-implement/gate-artifacts.ts +405 -0
  572. package/src/self-implement/gate-cli.ts +212 -18
  573. package/src/self-implement/gate-remote.ts +110 -25
  574. package/src/self-implement/gate-scenario.ts +85 -0
  575. package/src/self-implement/goal-author-decompose.ts +80 -16
  576. package/src/self-implement/goal-author.ts +270 -76
  577. package/src/self-implement/goal-authoring-grounding.ts +1 -1
  578. package/src/self-implement/goal-file-name.ts +65 -0
  579. package/src/self-implement/graph-authority.ts +38 -6
  580. package/src/self-implement/graph-overlay-condition.ts +92 -1
  581. package/src/self-implement/ground-goal.ts +2 -1
  582. package/src/self-implement/heal-triage.ts +79 -0
  583. package/src/self-implement/host-regate.ts +74 -2
  584. package/src/self-implement/orchestrator.ts +14 -123
  585. package/src/self-implement/pr-title.ts +123 -0
  586. package/src/self-implement/review-mustfix-class.ts +115 -0
  587. package/src/self-implement/run-checkpoint.ts +235 -0
  588. package/src/self-implement/run-ledger.ts +266 -17
  589. package/src/self-implement/run-stop.ts +4 -3
  590. package/src/self-implement/run-ttl.ts +28 -4
  591. package/src/self-implement/running-runs.ts +10 -6
  592. package/src/self-implement/seams.ts +2 -0
  593. package/src/self-implement/self-implement-runtime.ts +32 -0
  594. package/src/self-implement/shadow-stage.ts +7 -6
  595. package/src/self-implement/stop-autoheal.ts +2 -1
  596. package/src/self-implement/train-gate-remote.ts +37 -8
  597. package/src/self-implement/train-reach-grouping.ts +157 -0
  598. package/src/self-implement/train-scenario-layers.ts +133 -0
  599. package/src/self-implement/uncommitted-work.ts +2 -0
  600. package/src/session/pending-input.ts +81 -11
  601. package/src/session/session-start.ts +224 -0
  602. package/src/session-runtime/index.ts +7 -1
  603. package/src/session-runtime/parallel-dispatch.ts +22 -5
  604. package/src/skills/tools/dev-harness.ts +6 -4
  605. package/src/skills/tools/ops-ledger.ts +707 -0
  606. package/src/skills/tools/persistent-grounding.ts +47 -7
  607. package/src/skills/tools/shell.ts +117 -1
  608. package/src/status/bar.ts +32 -2
  609. package/src/status/status-provider-info.ts +58 -0
  610. package/src/steward/resync-tick.ts +114 -23
  611. package/src/steward/ta-repair-event-collect.ts +158 -0
  612. package/src/task-agent/card-evidence.ts +25 -1
  613. package/src/task-agent/green-sweep.ts +8 -3
  614. package/src/task-agent/judge.ts +76 -0
  615. package/src/task-agent/land-queue.ts +79 -0
  616. package/src/task-agent/live-moves.ts +30 -9
  617. package/src/task-agent/postland-card.ts +5 -0
  618. package/src/task-agent/prelaunch-already-done.ts +98 -0
  619. package/src/task-agent/real-launch-guard.ts +37 -0
  620. package/src/task-agent/rejudge-on-head.ts +93 -13
  621. package/src/task-agent/remote-rejudge-bind.ts +62 -13
  622. package/src/task-agent/repair-cycle.ts +58 -7
  623. package/src/task-agent/repair-worker.ts +7 -0
  624. package/src/task-agent/run-identity.ts +200 -0
  625. package/src/task-agent/shadow.ts +50 -3
  626. package/src/task-agent/task-hand.ts +158 -5
  627. package/src/task-agent/train-landing.ts +77 -0
  628. package/src/task-agent/work-order.ts +104 -0
  629. package/src/task-orchestrator/surfaces/kubectl-transient-retry.ts +51 -0
  630. package/src/task-orchestrator/surfaces/pod-command-job.ts +138 -22
  631. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +88 -19
  632. package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +2 -1
  633. package/src/task-orchestrator/surfaces/pod-image-source.ts +2 -1
  634. package/src/task-orchestrator/surfaces/pod-install-slots.ts +122 -15
  635. package/src/task-orchestrator/surfaces/pod-lease.ts +20 -6
  636. package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +169 -0
  637. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +103 -0
  638. package/src/task-orchestrator/surfaces/pod-result-marker-wait.ts +33 -0
  639. package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +63 -0
  640. package/src/task-orchestrator/surfaces/pod-source-receive.ts +19 -3
  641. package/src/task-orchestrator/surfaces/pod-warm-switch.ts +141 -0
  642. package/src/task-orchestrator/surfaces/pod-warm-ta-shadow.ts +85 -0
  643. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +153 -0
  644. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +208 -0
  645. package/src/task-orchestrator/surfaces/self-implement-pod.ts +237 -46
  646. package/src/telegram.ts +2 -4
  647. package/src/terminal/session-registry.ts +6 -0
  648. package/src/thinking-line.ts +12 -3
  649. package/src/tool-runtime/index.ts +4 -0
  650. package/src/tool-runtime/ops-ledger-runtimes.ts +84 -0
  651. package/src/tool-runtime/ref-consult-runtime.ts +17 -2
  652. package/src/tool-runtime/run-shell-runtime.ts +4 -1
  653. package/src/tool-runtime/self-cognition-runtimes.ts +5 -1
  654. package/src/tui/input/pickers.ts +732 -0
  655. package/src/tui/slash/mission.ts +492 -0
  656. package/src/tui/slash/term.ts +877 -0
  657. package/src/tui-client/dashboard-session.ts +9 -0
  658. package/src/tui.ts +53 -0
  659. package/src/user-config/gemini-server-tools.ts +9 -0
  660. package/src/user-config.ts +106 -21
  661. package/src/util/spawn-sync-output.ts +7 -0
  662. package/src/ux-sim/fake-bin.ts +154 -0
  663. package/src/ux-sim/fake-clock.ts +36 -0
  664. package/src/ux-sim/fake-llm.ts +29 -0
  665. package/src/ux-sim/headless-tui.ts +72 -0
  666. package/src/ux-sim/in-process-cli.ts +176 -0
  667. package/src/ux-sim/temp-git-remote.ts +82 -0
  668. package/src/version/packed-revision.json +1 -1
  669. package/src/video-pipeline/recipes/vlog.ts +25 -7
  670. package/apps/pwa/out/_next/static/chunks/1027.a0f04663d24c2779.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/1038.676007ae505822e7.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/1113.a37eadf7f740d955.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/112-fa7c526c20ce1405.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/1144.5fa1fa1d48dbcd12.js +0 -36
  675. package/apps/pwa/out/_next/static/chunks/119b405e.8fdd0e44d1ff37aa.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/1296.04560d98e73fbf51.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/1314-f604ba013ec535b0.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/1454-29d1d773d5996a49.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/1464.f3209af0d8ae4ff2.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/1729-d73dd25e564679d4.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/1843.5924c24295243261.js +0 -27
  682. package/apps/pwa/out/_next/static/chunks/1987.e28149724b92918b.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/2066-5ca18ffa5a4be68b.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/2240.d10a66695155fd93.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/2391.e2cbc98d4abbb41a.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/2566-734cb83bbd405886.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/275.0e6bad414729bfa0.js +0 -32
  688. package/apps/pwa/out/_next/static/chunks/2803-33bdae771855a8d1.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/3259-86f3be56e43eaf6a.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/3280-e177c33bfbdcfb0a.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/3294-2b6a6d45a46a6c1c.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/3522.758840fc5e7fe630.js +0 -287
  693. package/apps/pwa/out/_next/static/chunks/3525-a7c989c1a661e880.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/3694.b1a66387756458c0.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/370.124f9e678a76fc01.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/3797-9643105335e660fe.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/4084.15ecaf4240bd9e77.js +0 -286
  698. package/apps/pwa/out/_next/static/chunks/4089-b3382b8843114eee.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/4293.928a8d573c16aab7.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/4402.f9238a3b18a7bd43.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/4490-9b090b0f9427f902.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/4548.13502d0ca3b4f862.js +0 -8
  703. package/apps/pwa/out/_next/static/chunks/4565-67e4c620597965c8.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/4599-548bc96cda44a5a7.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/4625.206574e33a9e7ae8.js +0 -104
  706. package/apps/pwa/out/_next/static/chunks/4862-392c9a71f0d8dfb4.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/5054.2906d1b02542edc5.js +0 -149
  708. package/apps/pwa/out/_next/static/chunks/5102-c822bbbee5a022a4.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/5194-8c93f2e7e4b6f717.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/5305-bacd7eca0b9a5848.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/5311.9071ee3f44fd7ff5.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/5369.9f34353442daa421.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/589-49d730cb8138b397.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/5904-264a6412ae3c9028.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/5980.728531d5c566535c.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/599-56f2c0a5cc4a7295.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/6011.f525002e21bef0ac.js +0 -23
  718. package/apps/pwa/out/_next/static/chunks/6132.4381ab93683f1acf.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/6149.2e698635e454d8f6.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/6356-0afafc4230cf9767.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/6406.64756073f7df35b8.js +0 -77
  722. package/apps/pwa/out/_next/static/chunks/6479-16e62aedafa0e310.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/6599.b5246b46a2453329.js +0 -64
  724. package/apps/pwa/out/_next/static/chunks/6610.5c26eec011565acb.js +0 -1
  725. package/apps/pwa/out/_next/static/chunks/666.37d18474edc1c552.js +0 -2
  726. package/apps/pwa/out/_next/static/chunks/6904.a0b5b1ed4f2d2c20.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/7247-1f4aba94aba50d9f.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/7274-e71a7c5b7760cd02.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/7290.1fcaeef3adb5d0d4.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/7477-2c64e42760f82f33.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/7505-47002b76ff1aef6c.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/7600-11884ca4da68e5d9.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/7607.40d8bb3689697ccb.js +0 -62
  734. package/apps/pwa/out/_next/static/chunks/7763.f51fcc19ca1414ec.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/7785.846140c859b1adf6.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/7807-a0fdac6393e0bf36.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/7837-20a63b4b7e9342b1.js +0 -1
  738. package/apps/pwa/out/_next/static/chunks/800.7815414cf280545d.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/8085-1e5fce59500a2cc2.js +0 -1
  740. package/apps/pwa/out/_next/static/chunks/8100.415f2105faa63e97.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/8290.701aa42ed13aa767.js +0 -226
  742. package/apps/pwa/out/_next/static/chunks/83a80514-b897b64df43ce108.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/8444.d9c74214a3145a43.js +0 -93
  744. package/apps/pwa/out/_next/static/chunks/8569.98c915534928c7f9.js +0 -24
  745. package/apps/pwa/out/_next/static/chunks/8591.b1c7f876e5ec7cfb.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/8715-f63756021d3ab989.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/8879.776c2ec7732aa114.js +0 -115
  748. package/apps/pwa/out/_next/static/chunks/8881.ddca15b3c82c490c.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/8946.5da5c87f32a2e1a8.js +0 -41
  750. package/apps/pwa/out/_next/static/chunks/9117.080b9b75be90e14d.js +0 -74
  751. package/apps/pwa/out/_next/static/chunks/9167.f3ecb28f367d6e69.js +0 -94
  752. package/apps/pwa/out/_next/static/chunks/9224.66095370ee28c93b.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/9238-0682b013c83d6449.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/9449.d3ed795da4befb1c.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/9638-d268329f2c6f4f8d.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/973cbf1b.3bbcf2b41548eca7.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/9842-d6644eb53d3dc6bf.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/9863.94ca3263e760696e.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d9babd8795f396a5.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/app/decisions/page-256ffd365d32e100.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/app/exec/page-4c741eb760f2f77c.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/app/field/page-e9ba98f5f1440a21.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d9babd8795f396a5.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/intake/page-2f6f303fa114ec24.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/live-run/page-bb1f05cd51c38944.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f736324afa2117c1.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/morning/page-6f131b51193a36f9.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/not-found-435f5e2faf02bc01.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/app/page-66a67eb19ebd976b.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-c71267e7681da982.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/app/settings/page-781cfc5114e097da.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-478157214db6a023.js +0 -1
  794. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d9babd8795f396a5.js +0 -1
  795. package/apps/pwa/out/_next/static/chunks/app/setup/page-86141f12cf090b7f.js +0 -1
  796. package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +0 -1
  797. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +0 -1
  798. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +0 -1
  799. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +0 -1
  800. package/apps/pwa/out/_next/static/chunks/app/today/page-979d318a534e92da.js +0 -1
  801. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +0 -1
  802. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +0 -1
  803. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +0 -1
  804. package/apps/pwa/out/_next/static/chunks/app/workflows/page-601fb77340e63c9a.js +0 -1
  805. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +0 -1
  806. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +0 -1
  807. package/apps/pwa/out/_next/static/chunks/dcf24383-2f82e35f2d827393.js +0 -1
  808. package/apps/pwa/out/_next/static/chunks/framework-bf135b997155e90e.js +0 -1
  809. package/apps/pwa/out/_next/static/chunks/main-5571537543bea91c.js +0 -1
  810. package/apps/pwa/out/_next/static/chunks/main-app-25c51c7df7f3a442.js +0 -1
  811. package/apps/pwa/out/_next/static/chunks/pages/_app-e3b2c1f5d82abd65.js +0 -1
  812. package/apps/pwa/out/_next/static/chunks/pages/_error-083f2e23e9bc3641.js +0 -1
  813. package/apps/pwa/out/_next/static/chunks/webpack-e94d22b7a028217b.js +0 -1
  814. package/src/dashboard/bench-slash-runtime.ts +0 -40
  815. package/src/dashboard/input/search-modal-router.ts +0 -9
  816. package/src/dashboard/modals/pane-key-router.ts +0 -27
  817. package/src/dashboard/view-slash-runtime.ts +0 -48
  818. /package/apps/pwa/out/_next/static/{y1ltt7oCZxVpC6zVPhZ2O → LSXfY0u9QtfDbejY0Ukf_}/_ssgManifest.js +0 -0
@@ -6,10 +6,13 @@
6
6
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { fileURLToPath } from 'node:url';
9
+ import { createRunningBuildStatus } from './running-build-status.js';
10
+ import { reportTuiTurn } from '../pty-shell/tui-run-state.js';
11
+ import { logTurnThrew, runTurnTeardownStep } from './turn-busy-teardown.js';
9
12
  import { createStaleInstallCheck, formatStaleInstallNotice, readCurrentInstalledCommit, readDashboardBootCommit, STALE_INSTALL_CHECK_INTERVAL_MS, versionedDashboardInstallDir } from './stale-install-check.js';
10
13
  import { spawn, spawnSync } from 'node:child_process';
11
14
  import { $ } from 'bun';
12
- import { LOCAL_SKILLS_DIR, SERVICE_NAMES, DATA_DIR, OBSIDIAN_VAULT } from '../config.js';
15
+ import { LOCAL_SKILLS_DIR, DATA_DIR, OBSIDIAN_VAULT } from '../config.js';
13
16
  import { dispatchDashboardSessionRuntimeTool } from './session-runtime-dispatch.js';
14
17
  import { nextChatScrollOffset } from '../chat/log-scroll-key.js';
15
18
  import { suggestProjectForFolder } from '../project/project-store.js';
@@ -160,8 +163,8 @@ function loadLastSelection(): LastSelection | null {
160
163
  }
161
164
  import { getLocalSkills, executeSync } from '../sync.js';
162
165
  import { computeDiff } from '../smart.js';
163
- import { analyzeDiff, summarizeDiff, isAnalyzerAvailable } from '../grok.js';
164
- import { C, ICONS, ansi, initTui, closeTui, setExitNotice, readKey, setKeyTracer, render, resetRenderCache, termSize, pad, stripAnsi, timeSince, hLine, showHelp, invalidateRenderCacheRow, wrapAnsiByWidth, injectKey } from '../tui.js';
166
+ import { summarizeDiff, isAnalyzerAvailable } from '../grok.js';
167
+ import { C, ICONS, ansi, initTui, closeTui, setExitNotice, readKey, setKeyTracer, render, termSize, pad, stripAnsi, hLine, showHelp, invalidateRenderCacheRow, wrapAnsiByWidth, injectKey } from '../tui.js';
165
168
  import { paintCursor, type CursorState } from '../display/cursor-state.js';
166
169
  import type { CursorDecision } from '../display/cursor-owner.js';
167
170
  import type { Key, RenderOptions } from '../tui.js';
@@ -169,7 +172,6 @@ import {
169
172
  createTurnTypeaheadState,
170
173
  applyTurnTypeaheadKey,
171
174
  renderTurnTypeaheadQueueRow,
172
- drainTurnTypeaheadOnce,
173
175
  renderTurnTypeaheadEcho,
174
176
  } from '../chat/turn-typeahead.js';
175
177
  import { dispatchStreamingTurnTypeaheadSubmission } from './input/streaming-turn-typeahead.js';
@@ -180,13 +182,11 @@ import type { CoreTurnDispatchTool } from '../core-turn/index.js';
180
182
  // ACP pair (see `bootDashboardAcpSession`), which internally routes
181
183
  // to streamLLMWithTools via runCoreTurn.
182
184
  import {
183
- startSpinner,
184
185
  createChatState,
185
186
  buildContext,
186
187
  formatResponse,
187
188
  onEscAbort,
188
189
  attachStreamingKeys,
189
- SLASH_COMMANDS,
190
190
  type InputResult,
191
191
  type TextInputExternalSubmitRequest,
192
192
  } from '../chat/index.js';
@@ -198,6 +198,7 @@ import {
198
198
  } from '../chat/hud-segments-wire.js';
199
199
  import { PreviewTerminal } from '../preview/terminal.js';
200
200
  import { startThinking, startPinnedThinking, type ThinkingHandle } from '../thinking-line.js';
201
+ import { goalLoopIterationThinking } from './goal-loop-thinking.js';
201
202
  import { debug, getAmbientSessionId } from '../debug/log.js';
202
203
  import { startEventLoopStallMonitor, thinkingVerbFromFooter } from './event-loop-stall-monitor.js';
203
204
  import { getPushcutClient } from '../pushcut/client.js';
@@ -220,11 +221,6 @@ import {
220
221
  registerAction as inputCoreRegisterAction,
221
222
  addDefaultBinding as inputCoreAddDefaultBinding,
222
223
  initUserBindings as initInputCoreUserBindings,
223
- readAuditTail,
224
- isInputAuditEntry,
225
- formatAuditEntry,
226
- parseDuration as parseAuditDuration,
227
- runRebindCommand,
228
224
  type DispatchContext as InputCoreDispatchContext,
229
225
  type MouseInputEvent as InputCoreMouseInputEvent,
230
226
  type RouteCallbacks as InputCoreRouteCallbacks,
@@ -255,7 +251,6 @@ import { wireInputModeContextBridge } from '../input-core/mode-context-bridge.js
255
251
  import { getDashboardContextKeyService } from './context/keys.js';
256
252
  import {
257
253
  getDashboardContextMenuRegistry,
258
- handleForPill,
259
254
  } from './context-menu/registry.js';
260
255
  import { createTransientOverlayHost } from '../display/transient-overlay-host.js';
261
256
  import { configureThemeIconsGetter } from '../theme/icons.js';
@@ -267,7 +262,6 @@ import {
267
262
  } from '../log-pane/buffer-policy.js';
268
263
  import { publishFocusContextKeysFromService } from './context/focus-bridge.js';
269
264
  import {
270
- modalBlocksBackground,
271
265
  modalParticipatesInViewMode,
272
266
  renderModalStack,
273
267
  topBlockingForegroundModalSurface,
@@ -285,7 +279,6 @@ import {
285
279
  debugWorkbenchIndexForTarget,
286
280
  getDebugCompanionSpec,
287
281
  listDebugCompanionKeys,
288
- resolveDebugCompanionTargets,
289
282
  type DebugWorkbenchPane,
290
283
  } from '../window/debug-window-consumers.js';
291
284
  import {
@@ -295,7 +288,6 @@ import {
295
288
  } from '../window/tablet-workspace-policy.js';
296
289
  import {
297
290
  type PromptFrame,
298
- bottomFixedRowsForPromptFrame,
299
291
  resolvePromptFrame,
300
292
  } from '../display/prompt-frame.js';
301
293
  import { resolveBottomSlotBounds } from '../display/bottom-slot.js';
@@ -341,27 +333,8 @@ import { attachDashboardQuickPassConsumers } from './input/control-signal-consum
341
333
  import { resolveDashboardChatMainSlashCommand } from './input/chat-main-slash-command.js';
342
334
  import { TuiChatPersonaSelection, tuiChatPersonaMessages } from './slash-runtime/chat-persona.js';
343
335
  import { awaitGlobalPersonaLoad } from '../persona/global-registry.js';
344
- import { resolveDashboardChatMainCacheCommand } from './input/chat-main-cache-command.js';
345
- import {
346
- dashboardDeltaHelpLines,
347
- resolveDashboardChatMainDeltaCommand,
348
- } from './input/chat-main-delta-command.js';
349
- import {
350
- DASHBOARD_SETUP_STEPS,
351
- dashboardSetupHelpLines,
352
- resolveDashboardChatMainSetupCommand,
353
- } from './input/chat-main-setup-command.js';
354
336
  import { createDashboardInlineSetupFlow } from './setup-inline.js';
355
- import { resolveDashboardChatMainThemeCommand } from './input/chat-main-theme-command.js';
356
- import { resolveDashboardChatMainLogCommand } from './input/chat-main-log-command.js';
357
337
  import { noProviderReply } from './input/no-provider-reply.js';
358
- import {
359
- dashboardLogHelpLines,
360
- resolveDashboardLogFilterAction,
361
- resolveDashboardLogSearchAction,
362
- resolveDashboardLogSizeAction,
363
- resolveDashboardLogTurnAction,
364
- } from './input/chat-main-log-actions.js';
365
338
  import type { LogTurnSeparatorMode } from './log-turn-separator-mode.js';
366
339
  import { createDashboardChatMainInteractionOpts } from './input/chat-main-interaction-opts.js';
367
340
  import { createDashboardChatMainCompletionOpts } from './input/chat-main-completion-opts.js';
@@ -379,6 +352,7 @@ import {
379
352
  matchDashboardChordAction,
380
353
  } from './input/dashboard-chord-actions.js';
381
354
  import { createDashboardChordRuntime } from './input/dashboard-chord-runtime.js';
355
+ import { registerLogFoldInputBinding } from './input/log-fold-binding.js';
382
356
  import { handleDashboardDisplayKeyRoute } from './input/dashboard-display-key-route.js';
383
357
  import { routeDashboardPriorityKey } from './input/dashboard-priority-key-route.js';
384
358
  import { shouldPassthroughDashboardHostOwnedInputCoreAction } from './input/input-core-host-action.js';
@@ -425,7 +399,6 @@ import {
425
399
  } from './voice-host-boot.js';
426
400
  import {
427
401
  bootDashboardAutoTts,
428
- handleAutoTtsSlash,
429
402
  } from './auto-tts/auto-tts-host-boot.js';
430
403
  import {
431
404
  bootDashboardVoiceChat,
@@ -443,7 +416,6 @@ import {
443
416
  import {
444
417
  resolveLegacyForegroundModalFocusPlan,
445
418
  resolveLegacyForegroundModalDemotionTransition,
446
- resolveLegacyForegroundModalFallbackPane,
447
419
  } from './input/foreground-modal-focus.js';
448
420
  import {
449
421
  resolveDashboardInitialWorkingFocus,
@@ -460,18 +432,10 @@ import { addHint, listHints, removeHint, resetScope } from '../tool-hints/regist
460
432
  import { resetGateCache } from '../tool-hints/gate.js';
461
433
  import { collectSignals, signalsSummary } from '../tool-hints/signals.js';
462
434
  import { resetProbes } from '../tool-hints/probe.js';
463
- import {
464
- addAllowed,
465
- hostOf,
466
- listAllowed,
467
- rateLimitStatus,
468
- removeAllowed,
469
- } from '../tool-hints/api-allowlist.js';
470
435
  import type { HintScope } from '../tool-hints/types.js';
471
- import { listSkillNames, describeSkill } from '../skills/runner.js';
472
436
  import { globalAgentRegistry, type AgentToolCallEvent } from '../agent/registry.js';
473
437
  import { SELF_IMPLEMENT_TOOL_NAMES } from '../boot/daemon-tools/self-implement-names.js';
474
- import { FOLD_LIMITS, type FoldMode } from '../log-entry.js';
438
+ import { type FoldMode } from '../log-entry.js';
475
439
  import { renderToolCallEvent, renderToolResultVariants } from '../chat/tool-render/index.js';
476
440
  import { FoldStack } from '../fold-stack.js';
477
441
  import { getSkillIndex, reloadSkillIndex, applySkillFilter, skillIndexProblems } from '../skills/index.js';
@@ -546,7 +510,11 @@ export function formatEscAbortWaitingTargets(targets: readonly string[]): string
546
510
  export function settleEscAbortPendingLines(lines: string[], fromIndex: number): void {
547
511
  let settled = false;
548
512
  for (let index = fromIndex; index < lines.length;) {
549
- const line = lines[index]!;
513
+ const line = lines[index];
514
+ if (typeof line !== 'string') {
515
+ index += 1;
516
+ continue;
517
+ }
550
518
  const isAbortPending = line.startsWith(' ⏳ 중단 요청됨 — ');
551
519
  const isAbortRepeat = /^ ⏳ ESC 재시도 \d+회 — /.test(line);
552
520
  if (!isAbortPending && !isAbortRepeat) {
@@ -590,21 +558,50 @@ export function resumeInProcessDashboardSession(
590
558
  setAttachedSessionId: (sessionId: string) => void;
591
559
  setActiveSessionId: (sessionId: string) => void;
592
560
  },
593
- ): { resumed: true; sessionId: string; turns: number } | { resumed: false; reason: string } {
561
+ ):
562
+ | { resumed: true; sessionId: string; turns: number; repaired?: { removedCalls: number; removedResults: number } }
563
+ | { resumed: false; reason: string } {
594
564
  const resolvedId = deps.resolveSessionId(resumeSessionId);
595
565
  const loaded = resolvedId ? deps.historyFromSession(resolvedId) : null;
596
566
  if (!resolvedId || !loaded) return { resumed: false, reason: resumeSessionId };
597
567
 
568
+ // ④ 깨진 기록 방어(TUI-H8 · #69) — 도구 호출 도중 죽은 프로세스의 기록은 호출·결과 짝이 깨진 꼬리를 남겨
569
+ // 이어받은 첫 턴이 Codex 400 으로 죽는다. 고아 호출·결과만 잘라(`repairCodexToolPairs` · 짝이 맞는 것은 그대로)
570
+ // 이어받고, 같은 id 가 겹쳐 짝을 못 가리면 이어받지 않는다(새 세션 권고).
571
+ const pairs = repairCodexToolPairs(loaded.history as unknown as LLMMessage[]);
572
+ if (pairs.ambiguousIds.length > 0) {
573
+ try {
574
+ debug.log('session', 'resume-refused', {
575
+ surface: 'tui', sessionId: resolvedId, reason: 'ambiguous-tool-pairs', ambiguousIds: pairs.ambiguousIds.length,
576
+ });
577
+ } catch { /* observation is best-effort */ }
578
+ return { resumed: false, reason: `${resumeSessionId} — 도구 호출·결과 짝이 모호한 기록이다 · 새 세션으로 시작한다(권고)` };
579
+ }
580
+ const repairedCount = pairs.removedCalls + pairs.removedResults;
581
+ const history = repairedCount > 0 ? pairs.messages as unknown as ChatMessage[] : loaded.history;
582
+ if (repairedCount > 0) {
583
+ try {
584
+ debug.log('session', 'resume-history-repaired', {
585
+ surface: 'tui', sessionId: resolvedId, removedCalls: pairs.removedCalls, removedResults: pairs.removedResults,
586
+ });
587
+ } catch { /* observation is best-effort */ }
588
+ }
589
+
598
590
  const sysFromDash = deps.chatHistory.find((message) => message.role === 'system');
599
591
  deps.chatHistory.length = 0;
600
592
  if (sysFromDash) deps.chatHistory.push(sysFromDash);
601
- for (const message of loaded.history) {
593
+ for (const message of history) {
602
594
  if (message.role === 'system' && sysFromDash && message.content === sysFromDash.content) continue;
603
595
  deps.chatHistory.push(message);
604
596
  }
605
597
  deps.setAttachedSessionId(resolvedId);
606
598
  try { deps.setActiveSessionId(resolvedId); } catch { /* best-effort */ }
607
- return { resumed: true, sessionId: resolvedId, turns: loaded.history.length };
599
+ return {
600
+ resumed: true,
601
+ sessionId: resolvedId,
602
+ turns: history.length,
603
+ ...(repairedCount > 0 ? { repaired: { removedCalls: pairs.removedCalls, removedResults: pairs.removedResults } } : {}),
604
+ };
608
605
  }
609
606
 
610
607
  /**
@@ -625,38 +622,33 @@ export function getEscAbortRunningCount(
625
622
  return Math.max(1, registeredRunning);
626
623
  }
627
624
  }
628
- import { resetOnboardingMarker } from '../onboarding.js';
625
+ import { needsOnboarding, resetOnboardingMarker } from '../onboarding.js';
629
626
  import {
630
627
  getUserConfig, reloadUserConfig, saveUserConfig,
631
628
  rotateNextProvider, jumpToRotationEntry, rotationEntryLabel, applyRotationEntry,
632
- currentRotationIndex, resolveAcpHopCapFromConfig,
629
+ resolveAcpHopCapFromConfig,
633
630
  VOICE_HARDCODED_DEFAULTS,
634
631
  } from '../user-config.js';
635
- import { listProviders, streamLLM, anyProviderAvailable, getProviderForConfig, REASONING_CYCLE, nextReasoningLevel, reasoningLevelLabel, effectiveReasoningLevel, modelSupportsReasoning, type LLMMessage, type LLMToolSpec } from '../llm.js';
636
- import { resolveRouteDecision, recordCurrentRouteDecision, currentRouteDecision, formatRouteDecisionSummary } from '../llm/route-decision.js';
637
- import type { ReasoningLevel } from '../user-config.js';
632
+ import { streamLLM, anyProviderAvailable, getProviderForConfig, nextReasoningLevel, reasoningLevelLabel, effectiveReasoningLevel, modelSupportsReasoning, type LLMMessage, type LLMToolSpec } from '../llm.js';
633
+ import { currentRouteDecision, formatRouteDecisionSummary } from '../llm/route-decision.js';
634
+ import { resolveDashboardActiveModel } from './acp-active-model.js';
638
635
  import { getModelFamily, getModelTier } from '../models/prompts.js';
639
636
  import { buildPromptInjection, getPromptBankStore } from '../prompt-bank/index.js';
640
- import { getInputHistoryStore, inputHistoryDbPath, inputHistoryJsonPath } from '../input-history.js';
637
+ import { getInputHistoryStore } from '../input-history.js';
641
638
  import { inspectActiveProvider } from '../provider-summary.js';
639
+ import { createStatusProviderInfoReader } from '../status/status-provider-info.js';
642
640
  import { activeCodexAccountView } from '../oauth/codex-account-store.js';
643
- import { buildDashboardStatusLines } from './dashboard-status-lines.js';
644
- import { readSlashContextNow, seatsNowLine } from '../context-bus/context-now-surfaces.js';
641
+ import { buildDashboardStatusLines, readDashboardSeatsNow, type DashboardStatusLinesInput } from './dashboard-status-lines.js';
645
642
  import { buildFirstScreenBand, resolveTuiDaemonLink, type TuiDaemonLink } from './first-screen-band.js';
646
643
  import { detectLocale } from '../expression/locale.js';
647
644
  import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
648
645
  import {
649
- workingDirSegment, sessionCwdSegment, modelSegment, gitSegment,
650
- ctxBarSegment, costSegment, speedSegment, elapsedSegment, cacheSegment,
651
- ptyShellCountSegment,
652
- voiceCostSegment,
653
646
  type GitSegmentState,
654
647
  } from '../status/bar.js';
655
648
  // PR-S1V.5 — voice month-to-date USD pill on the secondary status row.
656
649
  // The tracker is process-wide so the same singleton is read here and
657
650
  // updated by `voice-input-bridge.transcribe` (TUI path) + daemon REST
658
651
  // handler (PWA path).
659
- import { globalVoiceCostTracker } from '../voice/cost-tracker.js';
660
652
  import { createDashboardMouseWiring } from './input/mouse-wiring.js';
661
653
  import { createMouseConversationPopupRuntime } from './input/mouse-conversation-popup-runtime.js';
662
654
  import { createMouseModalHitRuntime } from './input/mouse-modal-hit-runtime.js';
@@ -740,7 +732,7 @@ import { createDashboardContextMenuFeedbackRuntime } from './context-menu-feedba
740
732
  import { createDashboardStatusFeedbackRuntime } from './status-feedback-runtime.js';
741
733
  import { createDashboardTransferFeedbackRuntime } from './transfer-feedback-runtime.js';
742
734
  import { createDashboardFinderFeedbackRuntime } from './finder-feedback-runtime.js';
743
- import { createDashboardTermSlashRuntime } from './term-slash-runtime.js';
735
+ import { createDashboardTermSlashRuntime, resolveTuiShellSeat } from './term-slash-runtime.js';
744
736
  import { createDashboardShellSlashRuntime } from './shell-slash-runtime.js';
745
737
  import { createDashboardRunSkillSlashRuntime } from './run-skill-slash-runtime.js';
746
738
  import { createDashboardSkillTriggersSlashRuntime } from './skill-triggers-slash-runtime.js';
@@ -790,7 +782,6 @@ import { createPfcScreenshotAttacher, type PfcScreenshotAttacher } from '../cond
790
782
  import { createStatusShellPostureRuntime } from './status-shell-posture-runtime.js';
791
783
  import { createTerminalHostFacade } from '../shell-runner/terminal-host-facade.js';
792
784
  import {
793
- buildTerminalMouseIntentEvent,
794
785
  interpretTerminalSurfaceIntent,
795
786
  } from './terminal-surface-intent.js';
796
787
  import {
@@ -821,18 +812,16 @@ import { bootDashboardApproverRuntimes } from './approver-runtime-boot.js';
821
812
  import { bootDashboardCaptureSourceProviders } from './capture-source-provider-boot.js';
822
813
  import { bootDashboardContextMenuProviders } from './context-menu-provider-boot.js';
823
814
  import { bootDashboardContextMenuRegistry } from './context-menu-registry-boot.js';
824
- import { buildPillMenu } from '../ui/context-menu-registry.js';
825
- import { createActionPickerRecipe } from '../mouse-action-recipes.js';
826
- import type { PopupPlacement } from '../ui/chrome/picker-popup-placement.js';
827
- import { deriveSubmenuPopupRoleTheme } from '../ui/submenu-popup-theme.js';
828
815
  import { bootDashboardContextRuntime } from './context-runtime-boot.js';
829
816
  import { bootDashboardContextWindowRuntime } from './context-window-runtime-boot.js';
830
817
  import { bootDashboardEagerTools } from './eager-tool-boot.js';
831
818
  import { bootDashboardEmbodiedTools } from './embodied-tools-boot.js';
832
819
  import { bootDashboardHitl } from './hitl-boot.js';
820
+ import { bootBannerStatusLines, normalizeBootBannerMode, renderMinimalBootBand, type BootBannerHeld } from './boot-banner.js';
833
821
  import { attachDashboardFilePathToken } from './file-path-attachment.js';
834
822
  import { bootDashboardSlashExecutor } from './slash-executor-boot.js';
835
823
  import { buildDashboardSlashRegistry, type DashboardSlashContext } from './slash-runtime/index.js';
824
+ import type { LoopScheduler } from './loop-scheduler.js';
836
825
  import { unknownSlashReply } from './slash-runtime/unknown-slash-reply.js';
837
826
  import { resolveSurfaceUx } from '../agent/surface-ux/build.js';
838
827
  import type { SurfaceUx } from '../agent/surface-ux/types.js';
@@ -880,14 +869,14 @@ import {
880
869
  } from '../dashboard-pane-context-menu.js';
881
870
  // writeClipboard imported below with other clipboard helpers (line ~185).
882
871
  import {
883
- renderPaneNav, paneAtColumn, paneNavLabel,
872
+ paneAtColumn, paneNavLabel,
884
873
  type PaneNavHitArea,
885
874
  } from './panes/nav.js';
886
875
  import { getSessionCwd, setSessionCwd, subscribeSessionCwd, pickSwdTargetFromBrowser } from '../session/working-dir.js';
887
876
  import {
888
877
  getGitStatusView,
889
878
  subscribeGitChanges,
890
- refreshDirty as refreshGitDirty,
879
+ refreshDirtyAsync as refreshGitDirtyAsync,
891
880
  } from '../git-fs/index.js';
892
881
  import { getPty, listPty, requestPtyTakeover } from '../pty-shell/registry.js';
893
882
  import { listPtyManifestRowsAt } from '../pty-shell/pty-manifest.js';
@@ -899,16 +888,13 @@ import { formatPtyCallLine, formatPtyResultLine } from '../pty-activity-log.js';
899
888
  import { getSessionMetrics } from '../status/metrics.js';
900
889
  import {
901
890
  createContextRegistry, tokenizeInput, pruneUnreferenced,
902
- addAttachment as ctxAdd,
903
891
  addAttachmentEx as ctxAddEx,
904
- clearAll as ctxClearAll, clearLarge as ctxClearLarge,
905
892
  dropAttachment as ctxDrop, listAttachments as ctxList,
906
893
  totalContextBytes as ctxTotalBytes,
907
- DEFAULT_LARGE_THRESHOLD_BYTES,
908
894
  type Attachment,
909
895
  } from '../context.js';
910
896
  import { loadAllAttachments } from '../extractors.js';
911
- import { grabClipboardImage, pruneOldPastes, isClipboardSupported, writeClipboard, writeClipboardDetailed, hasClipboardImage } from '../clipboard/index.js';
897
+ import { grabClipboardImage, pruneOldPastes, isClipboardSupported, writeClipboard, writeClipboardDetailed } from '../clipboard/index.js';
912
898
  import { startClipboardHistory, type ClipboardHistory } from '../clipboard/history.js';
913
899
  import { renderImagePreview } from '../image/preview.js';
914
900
  import { isKgpTerminal } from '../kgp/capabilities.js';
@@ -917,23 +903,19 @@ import { _internal as previewRouterInternal } from '../preview/router.js';
917
903
  import { showPreviewModal } from './modals/preview.js';
918
904
  import { terminalModalRouter } from './input/terminal-modal-router.js';
919
905
  import {
920
- computeDashboardViewMode,
921
906
  syncDashboardViewModeToContextKeys,
922
907
  type DashboardViewModeSignals,
923
908
  } from './runtime/view-mode.js';
924
909
  import { IDLE_VIEW_MODE, type ViewMode } from '../input-core/view-mode.js';
925
- import { createInteractiveTerminalModal } from '../interactive-terminal-modal.js';
926
910
  import { showToast } from './render/toast.js';
927
911
  import {
928
912
  browserWidgetInstanceIdForView,
929
- shortcutFor,
930
913
  } from './modals/pane.js';
931
914
  import { showTransientTerminalModal, currentTransientTerminalModal } from './modals/transient.js';
932
- import { showPaneMultiModal, showLivePaneMultiModal, currentPaneMultiModal, type PaneMultiModalHandle } from './modals/pane-multi.js';
915
+ import { showLivePaneMultiModal, type PaneMultiModalHandle } from './modals/pane-multi.js';
933
916
  import {
934
917
  resolveCompanionPopupChrome,
935
918
  resolveCompanionPopupTitleControls,
936
- resolveWindowScopedCompanionPopupChrome,
937
919
  } from './modals/companion-popup-chrome.js';
938
920
  import {
939
921
  resolveDebugPaneMultiChrome,
@@ -951,30 +933,22 @@ import {
951
933
  BroadcastBus,
952
934
  PreviewSlotAdapter,
953
935
  getChannelBus,
954
- type TerminalPlacement,
955
936
  } from '../terminal-matrix/index.js';
956
- import { resolveTerminalMoveDestination } from '../terminal-matrix/mobility.js';
957
937
  import { initElementObservability } from '../element-registry/index.js';
958
938
  import {
959
- armSessionQuickControl,
960
939
  createSearchPlannerState,
961
940
  buildSessionRuntimeToolSpecs,
962
941
  buildSessionDashboardStatusLines,
963
- buildSessionSurfaceStatusLines,
964
942
  consumeSessionQuickControl,
965
943
  createSessionPostureState,
966
944
  enterSessionControlMode,
967
945
  exitSessionControlMode,
968
946
  isSessionControlActive,
969
947
  routeDevHarnessSubmitIntent,
970
- parseSessionControlSlash,
971
- parseSessionSurfaceSlash,
972
948
  resolveSessionInputModeFromChatMode,
973
949
  resolveSessionModeSnapshot,
974
950
  resolveSessionSurfaceStatus,
975
- setSessionPreferredSurface,
976
951
  toggleSessionControlMode,
977
- type SearchPlannerState,
978
952
  } from '../session-runtime/index.js';
979
953
  import {
980
954
  initDashboardApprovers,
@@ -987,12 +961,13 @@ import {
987
961
  bootDashboardAcpSession,
988
962
  deriveDaemonHttpBase,
989
963
  fetchDaemonSessionHistory,
990
- fetchDaemonSessionList,
991
964
  } from './acp-boot.js';
992
965
  import { renderReplayPreviewLines } from './replay-preview.js';
993
966
  import {
994
967
  buildDashboardTurnPreamble,
995
968
  } from './turn-preamble.js';
969
+ import { describeSeatBoot, startSeatBootConversation } from './seat-boot-preamble.js';
970
+ import { ensureSeatLocalInstructions, installSeatSessionRecorder } from './seat-local-instructions.js';
996
971
  import type { DashboardSession } from '../tui-client/dashboard-session.js';
997
972
  import type { SessionTurnProfile } from '../session-runtime/index.js';
998
973
  import { createCompanionSurfaceHostRegistry } from './companion-surface-host-registry.js';
@@ -1021,7 +996,6 @@ import {
1021
996
  } from '../display/workspace-affinity.js';
1022
997
  import { initDashboardHitl, stopDashboardHitl } from './runtime/hitl.js';
1023
998
  import { openDashboardShellRollupPopup } from './shell-rollup-popup.js';
1024
- import { expandPromptReferences } from '../prompt/references.js';
1025
999
  import { spawnCodingAgent, CodingAgentBinaryMissing } from '../terminal/coding-agent.js';
1026
1000
  import type { TerminalSession } from '../terminal/session-registry.js';
1027
1001
  import { createSessionPickerModal } from '../session/picker-modal.js';
@@ -1031,28 +1005,31 @@ import { listSessions as listChatSessionsForResume, adoptSession as adoptChatSes
1031
1005
  // 영속은 세션 스토어(forkSessionFromHistory)가 담당한다.
1032
1006
  import { userMessagePositions, truncateBeforeNthUser } from '../acp/session-fork.js';
1033
1007
  import { resolveEscEscRewind } from '../chat/esc-esc-rewind.js';
1034
- import { wirePersistence, loadPersistedSessions } from '../terminal/session-persistence.js';
1008
+ import { wirePersistence } from '../terminal/session-persistence.js';
1035
1009
  import { renderMarkdown as renderMarkdownGlow } from '../markdown-preview.js';
1036
1010
  import { basename, dirname } from 'path';
1037
1011
  import { statSync } from 'fs';
1038
1012
  import {
1039
- FIGURES, applyColor, toolHeader, toolResult, toolBlock,
1040
- renderTable, renderMarkdown, successLine, errorLine, warningLine,
1041
- sectionHeader, progressIndicator, renderTaskStatus,
1042
- type StatusColor, type TaskStatus,
1013
+ FIGURES, toolHeader, toolResult,
1014
+ renderMarkdown, successLine, errorLine, warningLine,
1043
1015
  } from '../render.js';
1044
1016
  import type { SyncMode } from '../types.js';
1045
1017
  import type { ChatMessage } from '../chat/index.js';
1018
+ import { repairCodexToolPairs } from './input/compact-prepass-recovery.js';
1019
+ import { observeSessionStart } from '../session/session-start.js';
1046
1020
  import type { PlainTurnSubmit } from '../input/turn-submit.js';
1047
1021
  // U-4.2 · `renderLogPane` no longer called from dashboard directly —
1048
1022
  // wd-log widget owns log rendering via renderLogViaWidget. Only
1049
1023
  // `findBlock` still used here (chat-input block navigation).
1050
- import { findBlock } from '../panes/log-pane.js';
1024
+ import { findBlock, logPaneContentHeight, maxLogPaneScroll, visibleSearchRowOffset } from '../panes/log-pane.js';
1051
1025
  import { createQuitConfirmationState, gracefulQuit, type QuitConfirmationState } from './graceful-quit.js';
1052
- import { enqueuePendingUserInput } from '../session/pending-input.js';
1026
+ import { consumeTurnEndInterrupted, handoffTurnTypeaheadAtTurnEnd, releaseTurnTypeaheadPending, runTurnTypeaheadPushNow, steerLastTurnTypeaheadSubmission } from './input/turn-steer-bridge.js';
1027
+ import { abortTurnOnly } from '../turn-abort-scope.js';
1028
+ import { consumeInternalTurnYield, createInternalTurnGate, formatInternalTurnBanner, resolveInternalTurnSubmitIntent, type InternalTurnRequest } from './input/internal-turn.js';
1029
+ import { createSeatAutowake } from './input/seat-autowake.js';
1053
1030
  import { createAttachmentRowMap } from '../log-pane/attachment-row-map.js';
1054
1031
  import { createAttachmentPopup, type AttachmentPopupAction } from '../log-pane/attachment-popup.js';
1055
- import { findLogMatches, type LogSearchResult } from '../log-pane/search.js';
1032
+ import { findLogMatches, findVisibleLogMatches, type LogSearchResult } from '../log-pane/search.js';
1056
1033
  import { createLogSearchModal, type LogSearchModalOpts } from '../log-pane/search-modal.js';
1057
1034
  // U-4.3 · direct imports of tryAttachmentHitAtBodyRow /
1058
1035
  // tryHandleLogAreaClick are gone · wd-log.onMouse owns the hit-test.
@@ -1081,7 +1058,6 @@ import { enqueueSoftStop, readSoftStopRequestStatus } from '../harness/control-i
1081
1058
  import { setSelfImplementSoftStopSpaceObserver } from '../self-implement/self-implement-runtime.js';
1082
1059
  import { createViEditor, type ViEditorHandle } from '../vi-editor.js';
1083
1060
  import { launchEditor, canLaunchEditor } from '../editor-launcher.js';
1084
- import { createSshPickerModal } from '../ssh/ssh-picker-modal.js';
1085
1061
  import {
1086
1062
  refreshRemoteWorkingDir,
1087
1063
  refreshRemoteWorkingDirPreview,
@@ -1091,12 +1067,10 @@ import {
1091
1067
  remoteBadge,
1092
1068
  } from '../ssh/remote-browser.js';
1093
1069
  import { editRemoteFile } from '../ssh/remote-edit.js';
1094
- import { scanFinder, relativizeResults } from '../finder/finder-scan.js';
1095
- import { scanRemoteFinder, relativizeRemoteResults } from '../finder/finder-scan-remote.js';
1096
- import { createFinderModal, createFolderPickerModal, type FinderItem } from '../finder/finder-modal.js';
1097
- import { homedir, hostname } from 'node:os';
1098
- import { listTransferTargets, type TransferTarget } from '../transfer/transfer-targets.js';
1099
- import { createTransferPickerModal } from '../transfer/transfer-picker-modal.js';
1070
+ import { createFolderPickerModal } from '../finder/finder-modal.js';
1071
+ import { hostname } from 'node:os';
1072
+ import { type TransferTarget } from '../transfer/transfer-targets.js';
1073
+ import { createInputPickers } from '../tui/input/pickers.js';
1100
1074
  import { sshTransfer, type SshTransferProgress } from '../transfer/ssh-transfer.js';
1101
1075
  import { iphoneTransfer } from '../transfer/iphone-transfer.js';
1102
1076
  import { DashboardAcpChat, displayBackendName, parseAcpSlash, type AcpBackendId } from './chat/acp-chat.js';
@@ -1111,7 +1085,6 @@ import {
1111
1085
  } from '../skills/tools/dashboard-config.js';
1112
1086
  import {
1113
1087
  initDashboardWidgetTools,
1114
- type WidgetInfo,
1115
1088
  } from '../skills/tools/dashboard-widget.js';
1116
1089
  import { initWidgetInspectorTools } from '../skills/tools/widget-inspector.js';
1117
1090
  import {
@@ -1124,7 +1097,6 @@ import {
1124
1097
  } from '../agent/spawn-embodied-agent-in-vw.js';
1125
1098
  import { findSessionObserver } from '../agent/observer-registry.js';
1126
1099
  import {
1127
- conversationHoverHudLabel,
1128
1100
  conversationModalTitle,
1129
1101
  conversationModalWidgetId,
1130
1102
  findConversationSessionEntry,
@@ -1142,7 +1114,6 @@ import {
1142
1114
  } from '../agent/channel-router.js';
1143
1115
  import { initTtySnapshotTools } from '../skills/tools/tty-snapshot.js';
1144
1116
  import { initAgentHandoffTool } from '../skills/tools/agent-handoff.js';
1145
- import { touchSshHost } from '../ssh/ssh-hosts.js';
1146
1117
  import { DisplayCoordinator } from '../display/coordinator.js';
1147
1118
  import {
1148
1119
  createLivePlaygroundHarness,
@@ -1170,10 +1141,10 @@ import {
1170
1141
  import { computeWorkingFocusSync } from '../display/working-focus-sync.js';
1171
1142
  import { dispatchPaneClick } from '../display/pane-click-dispatch.js';
1172
1143
  import { installResizeListener } from '../display/resize-listener.js';
1173
- import { isPtyForwardMouseEventType, type KeyEvent as DisplayKeyEvent } from '../display/types.js';
1144
+ import { isPtyForwardMouseEventType } from '../display/types.js';
1174
1145
  import { createDisplayEventBus } from '../display/events.js';
1175
1146
  import { agentBatchScratchTitle, renderAgentBatchScratch } from '../display/agent-batch.js';
1176
- import { createExecutionSurface, type ExecutionSurfaceHandle } from '../display/index.js';
1147
+ import { type ExecutionSurfaceHandle } from '../display/index.js';
1177
1148
  import { AgentSurfaceStore, renderAgentDetail, renderAgentRoster, type AgentSurfaceState } from '../display/agent-surface.js';
1178
1149
  import { globalAgentFlash } from '../display/agent-flash.js';
1179
1150
  import { renderDebugEventDetail, renderDebugEventRows, renderDebugStack, renderPromptInjectionDebug } from '../display/debug-surface.js';
@@ -1196,9 +1167,9 @@ export function registerDashboardCoreHostTools(host: Pick<PluginHost, 'registerH
1196
1167
  }
1197
1168
  }
1198
1169
  import { registerDashboardPaneHostTools } from './panes/host-tools.js';
1199
- import { DEFAULT_THEME_TOKENS, mergeThemeTokens, resolveThemeTokens, type ThemeTokenInput, type ThemeTokens } from '../theme/tokens.js';
1200
- import { resolveActiveTheme, setActivePresetInConfig } from './render/theme-resolver.js';
1201
- import { getTheme, listThemes } from '../themes/index.js';
1170
+ import { type ThemeTokens } from '../theme/tokens.js';
1171
+ import { resolveActiveTheme } from './render/theme-resolver.js';
1172
+ import { getTheme } from '../themes/index.js';
1202
1173
  import type { PaneFocus, PreviewSource, WorkingDirView } from '../workspace-types.js';
1203
1174
  import type { DisplayMouseEvent } from '../display/types.js';
1204
1175
  import type { Key as TuiKey } from '../tui.js';
@@ -1215,7 +1186,6 @@ import {
1215
1186
  readDirEntries,
1216
1187
  sortEntries,
1217
1188
  splitAtPrefix,
1218
- dirname as wdDirname,
1219
1189
  type WorkingDirState,
1220
1190
  type FsEntry,
1221
1191
  } from '../working-dir/index.js';
@@ -1224,9 +1194,6 @@ import {
1224
1194
  encodeBrowserScopedSubmitText,
1225
1195
  resolveBrowserActionContextFromHit,
1226
1196
  } from '../browser-pane/actions.js';
1227
- import { resolveBrowserTransferCapability } from '../browser-pane/capabilities.js';
1228
- import { openBrowserPaneModal } from '../browser-pane/mount.js';
1229
- import { openPreviewPaneModal } from '../preview-pane/mount.js';
1230
1197
  import {
1231
1198
  cyclePreviewSourceForView,
1232
1199
  formatPreviewSourceLabel,
@@ -1239,7 +1206,7 @@ import {
1239
1206
  } from '../preview-pane/model.js';
1240
1207
  import { PreviewPaneRegistry } from '../preview-pane/registry.js';
1241
1208
  import { FileIndexCache, absolutize } from '../file-index.js';
1242
- import { firstPaneOfView, paneSetForView } from '../working-dir/focus.js';
1209
+ import { firstPaneOfView } from '../working-dir/focus.js';
1243
1210
  import { applyVisibleFocusRepair } from './focus-repair.js';
1244
1211
  import {
1245
1212
  nextVisiblePaneFocus,
@@ -1250,11 +1217,7 @@ import {
1250
1217
  } from '../views/pane-policy.js';
1251
1218
  import {
1252
1219
  autoTabletModeForViewport,
1253
- orderDashboardViewsForCompactMode,
1254
1220
  effectiveChatOnlyModeForViewport,
1255
- orderSurfaceCatalogForCompactMode,
1256
- pruneDashboardViewsForCompactMode,
1257
- pruneSurfaceCatalogForCompactMode,
1258
1221
  productCompactModeForViewport,
1259
1222
  } from '../views/product-compact-mode.js';
1260
1223
  import { readScopedRenderLogs, resolveRenderSuppressed, readScopedDebugLevel, resolveStartupDebugLevel, hotPathGateOpen } from '../mss/logging/scoped-level.js';
@@ -1265,7 +1228,6 @@ import {
1265
1228
  compileDashboardViewLayout,
1266
1229
  describeDashboardViewsForPrompt,
1267
1230
  findDashboardView,
1268
- nextDashboardView,
1269
1231
  panesForDashboardView,
1270
1232
  serializeDashboardViewsConfig,
1271
1233
  validateDashboardViewsConfig,
@@ -1345,6 +1307,8 @@ import {
1345
1307
  type SpaceLongPressDetector,
1346
1308
  } from './input/space-longpress-detector.js';
1347
1309
  import { isAllowlistedDictationSurface } from './input/dictation-surface-allowlist.js';
1310
+ import { createDashboardExternalInputHost, stopTurnViaEscGate } from './input/external-input-host.js';
1311
+ import { registerExternalInputHost } from '../input/external-input-queue.js';
1348
1312
  // H3 #6 follow-up #3 — background sessions render as a distinct sidebar
1349
1313
  // kind with live state badge + tap-to-Join. Dashboard pulls BG records
1350
1314
  // through backgroundToStub + maps state via bgStateToSessionStatus,
@@ -1932,6 +1896,17 @@ export function renderDashboardFirstScreenBand(input: {
1932
1896
  return Promise.all([linkPending, pwaPending]).then(() => {});
1933
1897
  }
1934
1898
 
1899
+ /** The /status reader used by the live dashboard slash callback. */
1900
+ export function renderDashboardStatusLines(input: Omit<DashboardStatusLinesInput, 'seatsNow'>, now = Date.now()): string[] {
1901
+ let seatsNow: string | null = null;
1902
+ try {
1903
+ seatsNow = readDashboardSeatsNow(now);
1904
+ } catch (error) {
1905
+ debug.log('dashboard.status', 'seats-unreadable', { error: String(error) });
1906
+ }
1907
+ return buildDashboardStatusLines({ ...input, seatsNow });
1908
+ }
1909
+
1935
1910
  export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<DashboardAction> {
1936
1911
  let firstDashboardFrameRendered = false;
1937
1912
  await awaitGlobalPersonaLoad().catch(() => {});
@@ -2594,9 +2569,17 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2594
2569
  };
2595
2570
  const clearLogFilter = (): void => {
2596
2571
  logFilterQuery = '';
2572
+ if (logSearchQuery) {
2573
+ logSearchResults = findLogMatches(chatLines, logSearchQuery);
2574
+ logSearchCursor = Math.min(logSearchCursor, Math.max(0, logSearchResults.length - 1));
2575
+ }
2597
2576
  };
2598
2577
  const applyLogFilter = (query: string): void => {
2599
2578
  logFilterQuery = query.trim();
2579
+ if (logSearchQuery) {
2580
+ logSearchResults = findVisibleLogMatches(chatLines, logSearchQuery, logFilterQuery);
2581
+ logSearchCursor = Math.min(logSearchCursor, Math.max(0, logSearchResults.length - 1));
2582
+ }
2600
2583
  if (debug.enabled) {
2601
2584
  debug.log('log-pane.filter.apply', `q="${logFilterQuery}"`, {
2602
2585
  active: logFilterQuery.length > 0,
@@ -2606,7 +2589,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2606
2589
  const applyLogSearch = (query: string): void => {
2607
2590
  logSearchQuery = query.trim();
2608
2591
  logSearchResults = logSearchQuery
2609
- ? findLogMatches(chatLines, logSearchQuery)
2592
+ ? findVisibleLogMatches(chatLines, logSearchQuery, logFilterQuery)
2610
2593
  : [];
2611
2594
  logSearchCursor = 0;
2612
2595
  if (debug.enabled) {
@@ -2618,13 +2601,18 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2618
2601
  /** Center the viewport on a search-result line. Sets chatScrollOffset
2619
2602
  * so the match row lands mid-pane when possible; clamps at bounds. */
2620
2603
  const scrollToSearchResult = (result: LogSearchResult): void => {
2604
+ // An explicit search may jump to output that arrived while the pane
2605
+ // was frozen. Release that snapshot before computing the row bound.
2606
+ if (logFrozenTailIndex !== null && result.lineIdx >= logFrozenTailIndex) {
2607
+ logFrozenTailIndex = null;
2608
+ }
2621
2609
  const { rows: tr } = termSize();
2622
2610
  const sH = computeLogH(tr);
2623
- const bodyH = Math.max(0, sH - 1);
2624
2611
  const hasFooter = chatFooterLine.current != null && chatFooterLine.current !== '';
2625
- const contentH = Math.max(0, bodyH - (hasFooter ? 2 : 0));
2626
- const target = Math.max(0, result.lineIdx - Math.floor(contentH / 2));
2627
- const maxScroll = Math.max(0, chatLines.length - contentH);
2612
+ const hasQueueRow = hasFooter && !!renderTurnTypeaheadQueueRow(turnTypeaheadRef.state, Math.max(20, termSize().cols - 6));
2613
+ const contentH = logPaneContentHeight(sH, hasFooter, hasQueueRow);
2614
+ const target = Math.max(0, visibleSearchRowOffset(chatLines, result, logFilterQuery) - Math.floor(contentH / 2));
2615
+ const maxScroll = logScrollMax(sH);
2628
2616
  chatScrollOffset = target >= maxScroll ? -1 : target;
2629
2617
  // Entering a scrolled state may set the freeze — the
2630
2618
  // handleLogPaneKey transition only fires for user keystrokes, so
@@ -2836,6 +2824,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2836
2824
  // execute slashes, etc.
2837
2825
  const chatModeState = createSessionPostureState();
2838
2826
 
2827
+ // TUI-LOOP-COMMAND — 실행 중 스케줄러를 쥐는 ref. showDashboard 인스턴스당
2828
+ // 하나로, 입력 루프 «밖»에서 한 번만 선언한다. slashCtx 는 명령마다 다시
2829
+ // 만들어지므로 리터럴 옆에 선언하면 /loop start 로 만든 스케줄러 ref 가
2830
+ // 다음 명령에서 사라진다 — /loop stop · /session new|clear 세션 끝 정지가
2831
+ // 같은 ref 를 써야 «stop 0»(판정)가 성립한다.
2832
+ const dashboardLoopSchedulerRef: { current: LoopScheduler | null } = { current: null };
2833
+
2839
2834
  // T3-C2 — vi-editor mounted in the preview pane. Set by the
2840
2835
  // browser pane's `e` key on a file; cleared on :q / :q!. When
2841
2836
  // non-null it overrides the preview pane's normal render path.
@@ -2908,6 +2903,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
2908
2903
  // current value so log analysis can filter "what keys fired while
2909
2904
  // a turn was in flight".
2910
2905
  let streamingInFlight = false;
2906
+ /** 지금 턴의 escGate 중단 — 외부 `/stop`(TUI-STOP-INPUT-BYPASS)이 대기열을 건너뛰어 부른다. 턴 밖이면 null. */
2907
+ let currentTurnExternalStop: (() => void) | null = null;
2911
2908
  const resolveTmuxStatusLabel = (): string | null => {
2912
2909
  if (!process.env['TMUX']) return null;
2913
2910
  const fallback = process.env['TMUX_PANE'] ?? 'tmux';
@@ -3376,6 +3373,26 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3376
3373
  // 즉시 에코 · 턴 종료 시 loop-head 핸드오프가 prefill/자동제출로 넘긴다.
3377
3374
  // (외곽 스코프 선언 — 스트리밍 디스패처와 메인 키 루프 양쪽에서 참조.)
3378
3375
  const turnTypeaheadRef = { state: createTurnTypeaheadState() };
3376
+ // «지금 밀어 넣기»로 끊은 턴 — 턴 종료가 큐를 composer 로 «복원»하지 않고 곧바로 «제출»하게 한다.
3377
+ const steerInterruptRef = { pending: false };
3378
+ // ⭐ H2 내부 턴 문(TUI-H2-INTERNAL-TURN) — 자율 재개(H3·H4·H5)가 지나갈 «문 하나». 판정은 모듈,
3379
+ // 여기선 상태를 읽어 넘기기만 한다(⚠️ promptCtl·가시성 함수는 아래에서 선언 — 호출 시점엔 있다).
3380
+ const internalTurnGate = createInternalTurnGate({
3381
+ isTurnRunning: () => streamingInFlight,
3382
+ isInputForeground: () =>
3383
+ chatMainInputVisibilityState().inputOwnership?.owner === 'chat-main' && !topBlockingForegroundModal(),
3384
+ getBufferText: () => promptCtl.getBufferText?.(),
3385
+ getTypeaheadCount: () =>
3386
+ turnTypeaheadRef.state.queuedSubmissions.length + (turnTypeaheadRef.state.buffer ? 1 : 0),
3387
+ });
3388
+ // ⭐ H3 턴 끝 자동 재개(OPT-QUEUE-AUTOWAKE) — 자리 TUI ∧ autowake 파일일 때만. 턴 끝에 «남은 일»(안 읽은
3389
+ // 우편 · queue.md «## 대기»)을 재고 H2 문으로 다음 턴을 청한다. 판정·상한은 모듈, 여기선 배선만.
3390
+ const seatAutowake = createSeatAutowake({
3391
+ startTurnIfIdle: (text, opts) => internalTurnGate.startTurnIfIdle(text, opts),
3392
+ getCwd: () => workingDir.cwd,
3393
+ notify: (line) => { chatLines.push(C.muted(line)); draw(); },
3394
+ });
3395
+ const internalTurnYieldPort = seatAutowake.wrapPort(internalTurnGate.port);
3379
3396
  // ⭐ `B3` — 큐를 «도는 턴»에 넣으려면 그 턴이 도는 ***ACP 세션 id*** 가 필요하다.
3380
3397
  // ⛔ `attachedSessionId`(대화 세션)와 «다른 축»이다 — 루프는 ACP 세션으로 스코프를 연다.
3381
3398
  // ⚠️ 세션은 이 함수 «뒤»에서 만들어지므로 참조로 둔다(호출 시점엔 채워져 있다).
@@ -3416,6 +3433,16 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3416
3433
  // indicator below the scrolling log body. Mutated by startPinnedThinking
3417
3434
  // via this ref. `null` = no footer, body uses full log height.
3418
3435
  const chatFooterLine: { current: string | null } = { current: null };
3436
+ const logScrollMax = (height: number): number => {
3437
+ const hasFooter = chatFooterLine.current != null && chatFooterLine.current !== '';
3438
+ const hasQueueRow = hasFooter && !!renderTurnTypeaheadQueueRow(turnTypeaheadRef.state, Math.max(20, termSize().cols - 6));
3439
+ return maxLogPaneScroll(chatLines, height, {
3440
+ filterQuery: logFilterQuery,
3441
+ frozenTailIndex: logFrozenTailIndex,
3442
+ hasFooter,
3443
+ hasQueueRow,
3444
+ });
3445
+ };
3419
3446
  const disposeEventLoopStallMonitor = startEventLoopStallMonitor({
3420
3447
  getContext: () => ({
3421
3448
  streamingInFlight,
@@ -3538,21 +3565,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3538
3565
  * actual view model instead of drifting toward an old 60/40 guess.
3539
3566
  */
3540
3567
  const dashboardState = new DashboardStateStore();
3541
- const getFallbackPromptFrame = (termRows: number) =>
3542
- dashboardState.getFallbackPromptFrame(termRows);
3543
3568
  const getLayoutPromptFrame = (termRows: number): PromptFrame =>
3544
3569
  dashboardState.getLayoutPromptFrame(termRows);
3545
3570
  const currentPromptFrame = (termRows = termSize().rows): PromptFrame =>
3546
3571
  dashboardState.getCurrentPromptFrame(termRows);
3547
3572
 
3548
- /** Rows taken up by everything BELOW the log zone (hud excluded — it
3549
- * sits between grid and log, and is reserved separately when the
3550
- * grid is present). Used to compute paneH below and to size the
3551
- * embedded grid when logEmbedded=true. Must match the number of
3552
- * rows pushed by zones 'input-divider-upper' through 'dock'. */
3553
- const bottomFixedRows = (inputHeight: number) =>
3554
- bottomFixedRowsForPromptFrame(inputHeight);
3555
-
3556
3573
  /** Compute paneH (height reserved for the top pane/grid in
3557
3574
  * non-chat-only, non-embedded modes) given termRows. Factored out
3558
3575
  * so layoutTools.getCurrentLayout can reuse the same math. */
@@ -3687,6 +3704,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3687
3704
  // The ACP bridge is created later during dashboard boot. Keep status renders
3688
3705
  // safe before then, and bind this reader once the bridge exists.
3689
3706
  let activeAcpRuntimeModel = (): string | undefined => undefined;
3707
+ // 상태줄은 provider·model 만 쓴다 — 렌더마다 inspectActiveProvider(자격·authDetail 동기 읽기)를 안 부른다.
3708
+ const readStatusProviderInfo = createStatusProviderInfoReader();
3709
+ const runningBuildStatus = createRunningBuildStatus();
3690
3710
  /** Primary pill row — wd · git · provider/model · elapsed.
3691
3711
  * MX11b — delegates to mouseWiring.buildStatusLine so pill column
3692
3712
  * bounds are captured as a side effect; the return string is
@@ -3714,7 +3734,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3714
3734
  return mouseWiring.buildStatusLine({
3715
3735
  swd,
3716
3736
  gitState,
3717
- providerInfo: inspectActiveProvider(undefined, activeAcpRuntimeModel()),
3737
+ providerInfo: readStatusProviderInfo(activeAcpRuntimeModel()),
3718
3738
  contextUsedTokens: metrics.lastContextUsed,
3719
3739
  tmuxLabel: tmuxStatusLabel,
3720
3740
  sshLabel: sshStatusLabel,
@@ -3736,44 +3756,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3736
3756
  // dashboardVoiceChat.onPhaseChange). The status-bar pill is
3737
3757
  // intentionally null so we don't render the same state twice.
3738
3758
  voiceLabel: null,
3759
+ runningBuild: runningBuildStatus(),
3739
3760
  theme,
3740
3761
  });
3741
3762
  } catch { return ''; }
3742
3763
  };
3743
3764
 
3744
- /** Secondary metrics row — CTX bar · total cost · t/s · cache hit %.
3745
- * Reads from the session-metrics singleton populated by runTurn +
3746
- * dashboard streaming paths, plus the prompt-cache metrics
3747
- * singleton populated by onUsage. Renders an all-zero row when no
3748
- * turns have completed yet (matches Claude Code's "0k/N.Nk"
3749
- * placeholder). */
3750
- const buildSecondaryStatusLine = (): string => {
3751
- try {
3752
- const m = getSessionMetrics();
3753
- const theme = currentThemeTokens();
3754
- // Prompt-cache summary is ambient — import synchronously from a
3755
- // side-effect-free module; no fetch, no state beyond the session
3756
- // singleton that was already populated by onUsage events.
3757
- const { getSessionSummary } = require('../prompt-cache/metrics.js') as
3758
- typeof import('../prompt-cache/metrics.js');
3759
- const cachePct = getSessionSummary().hitRatePct;
3760
- // PR-S1V.5 — voice cost month-to-date pill. Reads the
3761
- // process-wide tracker that voice-input-bridge.transcribe and
3762
- // the daemon's /v1/voice/transcribe REST handler both write to,
3763
- // so the pill is the canonical view across surfaces. Empty
3764
- // string when nothing was spent this month.
3765
- const voiceMonthly = globalVoiceCostTracker().getMonthSummary().totalUsd;
3766
- const parts = [
3767
- ctxBarSegment(m.lastContextUsed, m.lastContextMax, 24),
3768
- costSegment(m.totalCostUsd, { theme }, m.unpricedTurns),
3769
- voiceCostSegment(voiceMonthly, { theme }),
3770
- speedSegment(m.lastTokensPerSec, { theme }),
3771
- cacheSegment(cachePct, { theme }),
3772
- ];
3773
- return parts.filter(Boolean).join(' ');
3774
- } catch { return ''; }
3775
- };
3776
-
3777
3765
  let draw: (opts?: { force?: boolean }) => void = () => {};
3778
3766
  let drawNow: (opts?: { force?: boolean }) => void = () => {};
3779
3767
  let drawQueued = false;
@@ -3930,9 +3918,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3930
3918
  if (debug.enabled) {
3931
3919
  debug.log('status.shell-posture.boot', 'ready', statusShellPostureRuntime.current());
3932
3920
  }
3933
- const blankRows = (height: number, width: number): string[] => (
3934
- Array.from({ length: Math.max(0, height) }, () => ' '.repeat(Math.max(0, width)))
3935
- );
3936
3921
  // IUL Phase M (Bundle 1) → Phase S·a (Bundle 2) → Bundle 3 wiring.
3937
3922
  // Wraps the existing afterRender hook with composeIdentityHooks so
3938
3923
  // every modal-kind surface mount/dispose gets a stable ModalIdentity
@@ -4064,9 +4049,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4064
4049
  // The 5s poll would eventually catch the change; forcing here
4065
4050
  // makes the "*N" suffix tick up within the same frame so the
4066
4051
  // user sees feedback for their own action immediately.
4067
- try {
4068
- refreshGitDirty(getSessionCwd(), { force: true });
4069
- } catch { /* noop */ }
4052
+ // TUI-TYPING-LAG — async probe: `git status` must not block key input.
4053
+ void refreshGitDirtyAsync(getSessionCwd(), { force: true })
4054
+ .then(() => { try { draw(); } catch { /* TUI torn down */ } }, () => { /* noop */ });
4070
4055
  });
4071
4056
  }
4072
4057
  // WD2 — session working directory subscription. Status bar pills
@@ -4098,10 +4083,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4098
4083
  // Kick off the first dirty probe in the background so the
4099
4084
  // "*N" suffix appears on the first meaningful redraw. Throttle
4100
4085
  // handles repeated calls; we just fire-and-forget.
4101
- try {
4102
- refreshGitDirty(swd, { force: true });
4103
- try { draw(); } catch { /* noop */ }
4104
- } catch { /* noop */ }
4086
+ void refreshGitDirtyAsync(swd, { force: true })
4087
+ .then(() => { try { draw(); } catch { /* noop */ } }, () => { /* noop */ });
4105
4088
  };
4106
4089
  wireGitForSwd();
4107
4090
 
@@ -4130,14 +4113,15 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4130
4113
  const gitDirtyTimer: ReturnType<typeof setInterval> = setInterval(() => {
4131
4114
  try {
4132
4115
  const before = getGitStatusView(getSessionCwd());
4133
- refreshGitDirty(getSessionCwd(), { minIntervalMs: 4_000 });
4134
- const after = getGitStatusView(getSessionCwd());
4135
- if (
4136
- (before.dirty?.total ?? -1) !== (after.dirty?.total ?? -1) ||
4137
- (before.aheadBehind?.ahead ?? -1) !== (after.aheadBehind?.ahead ?? -1)
4138
- ) {
4139
- try { draw(); } catch { /* noop */ }
4140
- }
4116
+ // TUI-TYPING-LAG — the 5s probe ran `git status` synchronously (130ms+, hundreds under load).
4117
+ void refreshGitDirtyAsync(getSessionCwd(), { minIntervalMs: 4_000 }).then((after) => {
4118
+ if (
4119
+ (before.dirty?.total ?? -1) !== (after.dirty?.total ?? -1) ||
4120
+ (before.aheadBehind?.ahead ?? -1) !== (after.aheadBehind?.ahead ?? -1)
4121
+ ) {
4122
+ try { draw(); } catch { /* noop */ }
4123
+ }
4124
+ }, () => { /* noop */ });
4141
4125
  } catch { /* noop */ }
4142
4126
  }, 5_000);
4143
4127
  (gitDirtyTimer as unknown as { unref?: () => void }).unref?.();
@@ -4398,6 +4382,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4398
4382
  // requestConfirmation() / HitlConfirm tool reaches the iPhone.
4399
4383
  // Fire-and-forget: a listener start failure warns but doesn't
4400
4384
  // block dashboard init (HITL degrades to terminal-only / no-op).
4385
+ // #28 — the minimal boot banner holds healthy-boot rows for /status instead of the transcript.
4386
+ const bootBannerMode = normalizeBootBannerMode(getUserConfig().dashboard.bootBanner);
4387
+ const bootBannerHeld: BootBannerHeld = {};
4401
4388
  bootDashboardHitl({
4402
4389
  enabled: process.env['ELANOUS_HITL_DISABLE'] !== '1',
4403
4390
  initDashboardHitl,
@@ -4407,6 +4394,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4407
4394
  try { return getPushcutClient().configured; } catch { return false; }
4408
4395
  },
4409
4396
  pushWarningLine: (message) => { chatLines.push(C.warning(message)); },
4397
+ holdWarningLine: bootBannerMode === 'minimal' ? (message) => { bootBannerHeld.hitl = message; } : undefined,
4410
4398
  draw: () => { draw(); },
4411
4399
  registerBeforeExit: (cb) => { process.once('beforeExit', cb); },
4412
4400
  warnConsole: (message) => { console.warn(message); },
@@ -4900,6 +4888,15 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4900
4888
  inputCoreAddDefaultBinding({ matcher: 'ctrl+x s', actionId: 'mode.enter.sync' });
4901
4889
  inputCoreAddDefaultBinding({ matcher: 'ctrl+x c', actionId: 'mode.enter.control' });
4902
4890
  inputCoreAddDefaultBinding({ matcher: 'ctrl+x g', actionId: 'mode.enter.general' });
4891
+ // #19 — the log-pane `F` bulk fold toggle, shared by the input (`ctrl+x f`) and pane chords.
4892
+ const toggleAllLogFolds = (): void => {
4893
+ const changed = foldStack.toggleAll();
4894
+ debug.log('log.fold', 'toggle-all', { via: 'ctrl+x f', changed });
4895
+ if (changed === 0) pushDebugLine(C.muted('(nothing to fold/unfold)'));
4896
+ chatScrollOffset = -1;
4897
+ draw();
4898
+ };
4899
+ registerLogFoldInputBinding(toggleAllLogFolds);
4903
4900
 
4904
4901
  initTui(true);
4905
4902
 
@@ -6246,14 +6243,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6246
6243
  const active = inspectActiveProvider(cfg);
6247
6244
  const reasoningProvider = (['openai-codex', 'openai', 'anthropic', 'gemini', 'grok', 'local', 'openrouter'] as const)
6248
6245
  .find(provider => active.provider === provider || active.provider === `auto:${provider}`);
6249
- let seatsNow: string | null = null;
6250
- try {
6251
- seatsNow = seatsNowLine(readSlashContextNow([]), Date.now());
6252
- } catch (error) {
6253
- debug.log('dashboard.status', 'seats-unreadable', { error: String(error) });
6254
- }
6255
- return buildDashboardStatusLines({
6256
- seatsNow,
6246
+ return renderDashboardStatusLines({
6257
6247
  provider: active.provider,
6258
6248
  model: active.model,
6259
6249
  reasoning: reasoningProvider
@@ -6263,6 +6253,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6263
6253
  : undefined,
6264
6254
  daemonAddress: opts.remote?.url,
6265
6255
  daemonConnected: daemonAttached,
6256
+ bootHeld: bootBannerStatusLines(bootBannerHeld),
6266
6257
  accountName: active.provider === 'openai-codex' || active.provider === 'auto:openai-codex'
6267
6258
  ? activeCodexAccountView().name : undefined,
6268
6259
  sessionSurfaceLines: buildSessionDashboardStatusLines(resolveSessionSurfaceStatus({ chatModeState })),
@@ -6337,7 +6328,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6337
6328
  const { rows: tr } = termSize();
6338
6329
  const sH = computeLogH(tr);
6339
6330
  const halfSH = Math.max(1, Math.floor(sH / 2));
6340
- const maxScr = Math.max(0, chatLines.length - sH);
6331
+ const maxScr = logScrollMax(sH);
6341
6332
  // Scroll keys → Scrollable behavior on wd-log. Sync pre/post.
6342
6333
  const SCROLL_KEYS = new Set([
6343
6334
  'j', 'k', 'down', 'up',
@@ -6527,7 +6518,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6527
6518
  const attachChatStreamingKeys = (abortCtrl: AbortController): () => void => {
6528
6519
  const chatLinesStartIndex = chatLines.length;
6529
6520
  userScrolledDuringStream = false;
6530
- streamingInFlight = true;
6531
6521
  const SCROLL_KEYS = new Set(['j', 'k', 'down', 'up', 'pageup', 'pagedown', 'home', 'end', 'g', 'G']);
6532
6522
  // T3-B2: ESC abort gate. When the user hits Esc AND sub-agent
6533
6523
  // work is running, pop a confirmation modal before tearing down
@@ -6597,7 +6587,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6597
6587
  const scrollLogBy = (delta: number): void => {
6598
6588
  const { rows: tr } = termSize();
6599
6589
  const sH = computeLogH(tr);
6600
- const maxScr = Math.max(0, chatLines.length - sH);
6590
+ const maxScr = logScrollMax(sH);
6601
6591
  const cur = chatScrollOffset < 0 ? maxScr : chatScrollOffset;
6602
6592
  let next = Math.max(0, Math.min(cur + delta, maxScr));
6603
6593
  if (delta > 0 && next >= maxScr) next = -1; // re-pin tail
@@ -6827,7 +6817,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6827
6817
  // ⛔ 중복 전송 방지가 계약이다: 레지스트리가 배수되면 `onDrained` 가
6828
6818
  // ***여기 큐도 같이 비운다*** — 안 그러면 같은 발화가 턴 끝에 «또» 나간다.
6829
6819
  // ⚠️ `immediate`(슬래시 등)는 지금 실행되므로 «넣지 않는다».
6830
- if (r.submissionDisposition === 'fifo' && !r.immediateSubmission) {
6820
+ // ⛔ H2 — 내부 턴 중의 사람 입력은 그 턴에 끼워 넣지 않는다(턴 끝 FIFO 로 기다린다).
6821
+ if (r.submissionDisposition === 'fifo' && !r.immediateSubmission && !internalTurnGate.isInternalTurnActive()) {
6831
6822
  const queuedText = turnTypeaheadRef.state.queuedSubmissions.at(-1);
6832
6823
  const sid = activeSessionIdForTypeahead();
6833
6824
  if (queuedText && sid) {
@@ -6842,22 +6833,38 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6842
6833
  },
6843
6834
  });
6844
6835
  if (queuedHistoryEntry) refreshInputHistory();
6845
- enqueuePendingUserInput(sid, queuedText, (drained) => {
6846
- // 배수된 만큼을 앞에서부터 떨어낸다(FIFO · 순서 보존).
6847
- const remaining = turnTypeaheadRef.state.queuedSubmissions.slice(drained.length);
6848
- turnTypeaheadRef.state = { ...turnTypeaheadRef.state, queuedSubmissions: remaining };
6849
- for (const text of drained) {
6836
+ // ⭐ 레지스트리 id 로 짝짓는다 — 배수·되꺼내기·턴 종료 승격이 같은 항목을 «한 번만» 보낸다.
6837
+ // ⛔ 슬래시는 안 넣는다(턴 종료 FIFO 로만 실행).
6838
+ steerLastTurnTypeaheadSubmission(turnTypeaheadRef, sid, ({ texts, remaining }) => {
6839
+ for (const text of texts) {
6850
6840
  echoSubmittedUserText(text, { live: true });
6851
6841
  debug.log('dashboard.turn-typeahead', 'echoed', { path: 'live', length: text.length });
6852
6842
  }
6853
- if (drained.length > 0) draw();
6843
+ if (texts.length > 0) draw();
6854
6844
  debug.log('dashboard.turn-typeahead', 'drained-into-live-turn', {
6855
- drained: drained.length, remaining: remaining.length,
6845
+ drained: texts.length, remaining,
6856
6846
  });
6857
6847
  });
6858
6848
  }
6859
6849
  }
6860
6850
  }
6851
+ if (r.releasedPendingIds) releaseTurnTypeaheadPending(r.releasedPendingIds, 'recalled');
6852
+ if (r.pushNowRequested) {
6853
+ const decision = runTurnTypeaheadPushNow(turnTypeaheadRef, {
6854
+ runningToolNames: () => getEscAbortWaitingToolNames(escAbortToolState),
6855
+ interruptTurn: () => {
6856
+ steerInterruptRef.pending = true;
6857
+ try { globalAgentRegistry.markBackgroundAllRunning(); } catch { /* 승격 실패가 끊기를 막지 않는다 */ }
6858
+ abortTurnOnly(abortCtrl);
6859
+ },
6860
+ pushNotice: (line) => pushChatLine(C.muted(line)),
6861
+ });
6862
+ debug.log('dashboard.turn-typeahead', 'push-now', {
6863
+ kind: decision.kind,
6864
+ tools: decision.kind === 'interrupt' ? decision.runningTools : decision.kind === 'nothing' ? [] : decision.blockingTools,
6865
+ });
6866
+ draw();
6867
+ }
6861
6868
  if (r.immediateSubmission) {
6862
6869
  turnTypeaheadRef.state = await dispatchStreamingTurnTypeaheadSubmission(
6863
6870
  turnTypeaheadRef.state,
@@ -7083,12 +7090,30 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
7083
7090
  // `runStreamingKeyUnifiedDispatch` above for the route body.
7084
7091
  await runStreamingKeyUnifiedDispatch(key);
7085
7092
  });
7093
+ // ⛔ TUI-STALE-BUSY-MARKER — busy 는 «끄는 closure 를 돌려주기 직전»에 켠다. 위 준비(ESC 게이트·키 리스너)가
7094
+ // 던지면 호출자는 cleanup 을 못 받으므로, 먼저 켜 두면 아무도 못 끄는 busy 가 남는다(#37).
7095
+ streamingInFlight = true;
7096
+ reportTuiTurn('busy');
7097
+ const externalStop = (): void => { stopTurnViaEscGate(escGate); };
7098
+ currentTurnExternalStop = externalStop;
7086
7099
  return () => {
7087
- cleanup();
7088
- escGate.dispose();
7089
- if (!stoppedHarnessSpace) settleEscAbortPendingLines(chatLines, chatLinesStartIndex);
7090
- userScrolledDuringStream = false;
7091
- streamingInFlight = false;
7100
+ if (currentTurnExternalStop === externalStop) currentTurnExternalStop = null;
7101
+ // 단계마다 격리 — 앞 단계가 던져도 settle·idle 전이에 닿는다(키 층 유휴 ∧ 턴 층 busy 이원 판정 #55 차단).
7102
+ runTurnTeardownStep('stream-keys-detach', cleanup);
7103
+ runTurnTeardownStep('esc-gate-dispose', () => { escGate.dispose(); });
7104
+ try {
7105
+ if (!stoppedHarnessSpace) settleEscAbortPendingLines(chatLines, chatLinesStartIndex);
7106
+ } catch (err) {
7107
+ const message = err instanceof Error ? err.message : String(err);
7108
+ const stackHead = err instanceof Error ? err.stack?.split('\n')[1]?.trim() : undefined;
7109
+ debug.log('dashboard.turn', 'settle-failed', { message, stackHead });
7110
+ logTurnThrew('settle-esc-lines', err);
7111
+ } finally {
7112
+ userScrolledDuringStream = false;
7113
+ streamingInFlight = false;
7114
+ reportTuiTurn('idle');
7115
+ seatAutowake.noteTurnEnded();
7116
+ }
7092
7117
  };
7093
7118
  };
7094
7119
 
@@ -7332,9 +7357,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
7332
7357
  ?? viewRegistry.views[0]!;
7333
7358
  setActiveDashboardView(def);
7334
7359
  };
7335
- const reloadDashboardViews = (): void => {
7336
- applyDashboardViewsRuntime(getUserConfig().dashboard.views as any);
7337
- };
7338
7360
  const userClosedPanes = new Set<PaneFocus>();
7339
7361
  const closedPaneSet = (): ReadonlySet<PaneFocus> => {
7340
7362
  const closed = new Set(userClosedPanes);
@@ -8298,7 +8320,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
8298
8320
  }
8299
8321
  return;
8300
8322
  }
8301
- const maxScr = Math.max(0, chatLines.length - logViewport.height);
8323
+ const maxScr = logScrollMax(logViewport.height);
8302
8324
  const delta = m.type === 'scroll-up' ? -3 : 3;
8303
8325
  const before = chatScrollOffset;
8304
8326
  chatScrollOffset = Math.max(0, Math.min(
@@ -9367,24 +9389,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
9367
9389
  limit,
9368
9390
  });
9369
9391
  };
9370
- const describePaneStateForPrompt = (): string => {
9371
- const state = paneStateSnapshot();
9372
- const rows = state.panes.map(p => {
9373
- const bits = [
9374
- p.visible ? 'visible' : 'hidden',
9375
- p.closed ? 'closed' : '',
9376
- p.closeable ? 'closeable' : 'primary',
9377
- p.omittedReason ? `omitted=${p.omittedReason}` : '',
9378
- ].filter(Boolean).join(', ');
9379
- return `- ${p.pane}: ${bits}`;
9380
- });
9381
- return [
9382
- '## Dashboard Pane State',
9383
- '',
9384
- `Active view: ${state.activeViewId} "${state.viewLabel}" (baseView=${state.baseView}, compact=${state.compactLevel}, focus=${state.focused})`,
9385
- ...rows,
9386
- ].join('\n');
9387
- };
9388
9392
  // Surface-unification v2.2 V2.2-5 Part 2 (2026-05-11) — schedulerView
9389
9393
  // state · openSchedulerForm · activeSchedulerFormValue /
9390
9394
  // setActiveSchedulerFormValue · submitSchedulerForm 모두 폐기.
@@ -9446,11 +9450,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
9446
9450
  );
9447
9451
  };
9448
9452
 
9449
- // Plain row label used as the list widget's identity (selection
9450
- // matching, cursor display string). Kept ANSI-free so visibleWidth
9451
- // and the selected-set string lookup work correctly.
9452
- const fmtEntry = (e: FsEntry): string => e.isDir ? `${e.name}/` : e.name;
9453
-
9454
9453
  // Colored variant for the list widget's `items` array — yazi-style
9455
9454
  // accent for folders, per-extension fileColor for files. The list
9456
9455
  // widget renders these straight through when preserveAnsi is true.
@@ -9887,92 +9886,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
9887
9886
  );
9888
9887
  };
9889
9888
 
9890
- // ── Workspace context builder ──
9891
- // Emitted once per chat turn (prepended to the user message) so the
9892
- // LLM knows what widgets are currently on screen and how to write
9893
- // to them via layout_setWidgetState / layout_addWidget without
9894
- // having to probe layout_getState first. Kept compact — iteration
9895
- // order matches visual layout (top-to-bottom, left-to-right).
9896
- //
9897
- // Writable surfaces:
9898
- // wd-scratch — primary canvas for notes / reports. Persists
9899
- // until the user clears or switches to memo mode.
9900
- // wd-preview — secondary canvas. Persists until the user
9901
- // navigates to a different file in the browser
9902
- // (file nav overwrites, which is by design).
9903
- //
9904
- // Reserved (LLM writes rejected with a helpful error):
9905
- // wd-browser, wd-log — dashboard re-renders these every frame
9906
- // from its own buffers (file list, chat
9907
- // transcript). Use layout_addWidget to
9908
- // spawn a fresh widget the dashboard will
9909
- // NOT touch.
9910
- const buildWorkspaceContext = (): string => {
9911
- try {
9912
- const pluginLayout = pluginHost.activeLayout();
9913
- let layoutForCtx: Layout;
9914
- if (pluginLayout) {
9915
- layoutForCtx = pluginLayout;
9916
- } else {
9917
- const { cols, rows: tr } = termSize();
9918
- const promptFrame = getLayoutPromptFrame(tr);
9919
- const topHeight = computePromptFrameGridHeight(tr, promptFrame);
9920
- layoutForCtx = workingDirLayoutForView(cols - 1, topHeight);
9921
- }
9922
- const visibleWidgets: Array<{ id: string; type: string; title?: string }> = [];
9923
- for (const row of layoutForCtx.rows) {
9924
- for (const cell of row.cells) {
9925
- if (!cell.widgetInstanceId) continue;
9926
- const inst = widgetHost.get(cell.widgetInstanceId);
9927
- if (!inst) continue;
9928
- visibleWidgets.push({
9929
- id: inst.id,
9930
- type: inst.type,
9931
- title: inst.character || undefined,
9932
- });
9933
- }
9934
- }
9935
- if (visibleWidgets.length === 0) return '';
9936
- const rows = visibleWidgets.map(w => {
9937
- const tag =
9938
- w.id === 'wd-scratch' ? ' ← writable canvas; persistent'
9939
- : w.id === 'wd-preview' ? ' ← writable canvas; overwritten on file-nav'
9940
- : w.id === 'wd-browser' ? ' ← READ-ONLY (dashboard-managed file list)'
9941
- : w.id === 'wd-log' ? ' ← READ-ONLY (chat transcript)'
9942
- : w.id.startsWith('wd-debug-') ? ' ← READ-ONLY (debug event surface; use debug_* tools)'
9943
- : ' ← persistent (spawned by LLM)';
9944
- const label = w.title ? ` "${w.title}"` : '';
9945
- return `- ${w.id} (${w.type})${label}${tag}`;
9946
- }).join('\n');
9947
- return [
9948
- '## Current dashboard workspace',
9949
- '',
9950
- 'The following widgets are visible right now. You have three host-level tools to mutate the layout:',
9951
- '- `layout_getState` — refresh view of the grid.',
9952
- '- `layout_setWidgetState({id, patch})` — merge a patch into a widget\'s state. For markdown widgets patch `{text: "..."}`.',
9953
- '- `layout_addWidget({type, row, col, character, config})` — spawn a new widget; dashboard will NOT re-render its state.',
9954
- '',
9955
- rows,
9956
- '',
9957
- 'Writing rules:',
9958
- '- To SHOW the user multi-line output (notes, analyses, lists, reports), write to `wd-scratch` via `layout_setWidgetState({id:"wd-scratch", patch:{text:"<content>"}})`. The dashboard auto-reopens that pane if the user had it closed.',
9959
- '- For a secondary canvas (e.g. side-by-side comparison), write to `wd-preview` the same way.',
9960
- '- DO NOT write to `wd-browser` or `wd-log` — the dashboard rejects those patches with an error.',
9961
- '- DO NOT write to `wd-debug-*`; use `debug_getState`, `debug_getCallStack`, `debug_setLevel`, `debug_openView`, and `debug_selectEvent`.',
9962
- '- For your own persistent widget (won\'t be overwritten by anything), spawn with `layout_addWidget` and pick your own id.',
9963
- '',
9964
- describeDashboardViewsForPrompt(viewRegistry),
9965
- '',
9966
- describePaneStateForPrompt(),
9967
- ].join('\n');
9968
- } catch {
9969
- // Workspace snapshot is best-effort — a failure here must not
9970
- // break the chat turn. Return empty so the user message flows
9971
- // through unchanged.
9972
- return '';
9973
- }
9974
- };
9975
-
9976
9889
  // ── LLM layout tools (W5) ──
9977
9890
  // Host-level tools that let the chat LLM reshape the dashboard.
9978
9891
  // When a plugin is active the tools edit its layout directly; when
@@ -11733,63 +11646,53 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11733
11646
  let agentSearchModal: SearchModalHandle | null = null;
11734
11647
  let agentSearchHandle: { dispose: () => void } | null = null;
11735
11648
 
11736
- /** Open the agent-roster search modal. Reads the current roster
11737
- * from agentSurfaceStore + globalAgentRegistry. Items: each
11738
- * entry's name + last 8 chars of id. Enter sets cursor; Esc
11739
- * dismisses. */
11740
- function openAgentRosterSearch(): void {
11741
- if (agentSearchModal) return;
11742
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11743
- const cols = termSize().cols;
11744
- const modalWidth = Math.min(60, cols - 4);
11745
- const modalRow = 3; // top-anchored over the roster pane
11746
- const modalCol = Math.max(2, Math.floor((cols - modalWidth) / 2));
11747
- const filterable = roster.length > 3;
11748
- const handle = createSearchModal({
11749
- id: `agent-roster-search:${Date.now().toString(36)}`,
11750
- bounds: { row: modalRow, col: modalCol, width: modalWidth, height: 12 },
11751
- title: 'Search agents',
11752
- width: modalWidth,
11753
- maxVisible: 8,
11754
- primaryActionLabel: 'jump',
11755
- cancelActionLabel: 'cancel',
11756
- filterable,
11757
- actionButtons: true,
11758
- chromeSpec: {
11759
- titleAlign: 'center',
11760
- },
11761
- onQuery: (q) => {
11762
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11763
- const needle = q.trim().toLowerCase();
11764
- const match = (t: typeof roster[number]) => {
11765
- if (!needle) return true;
11766
- return t.name.toLowerCase().includes(needle)
11767
- || t.definitionName.toLowerCase().includes(needle)
11768
- || (t.summary ?? '').toLowerCase().includes(needle)
11769
- || t.id.toLowerCase().includes(needle);
11770
- };
11771
- return roster.filter(match).map((t): SearchItem => ({
11772
- label: `${t.name} ${t.definitionName} ${t.id.slice(0, 8)}`,
11773
- payload: t.id,
11774
- }));
11775
- },
11776
- onAccept: (item) => {
11777
- const roster = agentSurfaceStore.syncTasks(globalAgentRegistry.list());
11778
- const idx = roster.findIndex(t => t.id === item.payload);
11779
- if (idx >= 0) {
11780
- agentRosterCursor = idx;
11781
- agentCursorLockedId = roster[idx]!.id;
11782
- agentCursorManual = true;
11783
- }
11784
- closeAgentRosterSearch();
11785
- },
11786
- onCancel: () => closeAgentRosterSearch(),
11787
- theme: currentThemeTokens(),
11788
- });
11789
- attachSurfaceToWorkspace(handle.surface, currentWorkspaceOwnerId());
11790
- agentSearchModal = handle;
11791
- agentSearchHandle = display.pushModal(handle.surface);
11792
- }
11649
+ // TUI-DIET-SEAM-PICKERS — the five input pickers (agent-roster search,
11650
+ // transfer, Ctrl+T file finder, Alt+C directory finder, Ctrl+K SSH)
11651
+ // live in src/tui/input/pickers.ts. Mutable dashboard state crosses
11652
+ // the seam as live get/set accessors (never a value copy); `draw` is
11653
+ // re-read at call time because it is reassigned after mount.
11654
+ const {
11655
+ openAgentRosterSearch,
11656
+ openTransferPicker,
11657
+ openFinderPicker,
11658
+ openDirectoryPicker,
11659
+ openSshPicker,
11660
+ } = createInputPickers({
11661
+ state: {
11662
+ get agentSearchModal() { return agentSearchModal; },
11663
+ set agentSearchModal(v: SearchModalHandle | null) { agentSearchModal = v; },
11664
+ get agentSearchHandle() { return agentSearchHandle; },
11665
+ set agentSearchHandle(v: { dispose: () => void } | null) { agentSearchHandle = v; },
11666
+ get agentRosterCursor() { return agentRosterCursor; },
11667
+ set agentRosterCursor(v: number) { agentRosterCursor = v; },
11668
+ get agentCursorLockedId() { return agentCursorLockedId; },
11669
+ set agentCursorLockedId(v: string) { agentCursorLockedId = v; },
11670
+ get agentCursorManual() { return agentCursorManual; },
11671
+ set agentCursorManual(v: boolean) { agentCursorManual = v; },
11672
+ get chatScrollOffset() { return chatScrollOffset; },
11673
+ set chatScrollOffset(v: number) { chatScrollOffset = v; },
11674
+ get chatLines() { return chatLines; },
11675
+ },
11676
+ display,
11677
+ agentSurfaceStore,
11678
+ workingDir,
11679
+ dockedPreview,
11680
+ transferFeedbackRuntime,
11681
+ finderFeedbackRuntime,
11682
+ currentThemeTokens,
11683
+ currentWorkspaceOwnerId,
11684
+ activeBrowserWidgetId,
11685
+ resolveBrowserStateById,
11686
+ fmtBytes,
11687
+ runTransfer,
11688
+ closeAgentRosterSearch,
11689
+ pushDebugLine,
11690
+ refreshRemotePreviewBridge,
11691
+ refreshWorkingDirPreview,
11692
+ setWorkingFocus,
11693
+ startRemoteModeBridge,
11694
+ draw: () => draw(),
11695
+ });
11793
11696
 
11794
11697
  function closeAgentRosterSearch(): void {
11795
11698
  if (agentSearchHandle) { agentSearchHandle.dispose(); agentSearchHandle = null; }
@@ -11797,75 +11700,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11797
11700
  draw();
11798
11701
  }
11799
11702
 
11800
- /** T5-J5 — open the transfer picker. Files arg = local paths to
11801
- * ship; destination comes from the picker. Handles both ssh and
11802
- * iphone target kinds. Remote-mode browser isn't supported here
11803
- * yet — the user is transferring LOCAL files (focused entry +
11804
- * selected set) to a remote destination. Transferring REMOTE→X
11805
- * is a separate flow (not this phase). */
11806
- function openTransferPicker(
11807
- files: Array<{ localPath: string; size?: number }>,
11808
- browserId: string = activeBrowserWidgetId(),
11809
- ): void {
11810
- const targetBrowser = resolveBrowserStateById(browserId) ?? (workingDir as BrowserPaneModel);
11811
- if (files.length === 0) {
11812
- transferFeedbackRuntime.onNoFilesToTransfer();
11813
- return;
11814
- }
11815
- const capability = resolveBrowserTransferCapability(
11816
- targetBrowser,
11817
- listTransferTargets(),
11818
- );
11819
- if (!capability.enabled) {
11820
- transferFeedbackRuntime.onTransferDisabled(capability.reason);
11821
- return;
11822
- }
11823
- const targets = capability.targets;
11824
- if (targets.length === 0) {
11825
- transferFeedbackRuntime.onNoTransferTargets();
11826
- return;
11827
- }
11828
- const totalBytes = files.reduce((a, f) => a + (f.size ?? 0), 0);
11829
- const summary = `${files.length} file${files.length === 1 ? '' : 's'}${
11830
- totalBytes > 0 ? ` (${fmtBytes(totalBytes)})` : ''
11831
- }`;
11832
-
11833
- const { cols, rows } = termSize();
11834
- const width = Math.min(74, Math.max(46, cols - 6));
11835
- const visibleRows = Math.min(10, targets.length);
11836
- const height = visibleRows + 3;
11837
- const bounds = {
11838
- row: Math.max(1, Math.floor((rows - height) / 2)),
11839
- col: Math.max(1, Math.floor((cols - width) / 2)),
11840
- width,
11841
- height,
11842
- };
11843
- let handle: ReturnType<typeof display.pushModal> | null = null;
11844
- const picker = createTransferPickerModal({
11845
- targets,
11846
- bounds,
11847
- width,
11848
- maxVisible: visibleRows,
11849
- summary,
11850
- theme: currentThemeTokens(),
11851
- onAccept: (target) => {
11852
- agentSearchModal = null;
11853
- handle?.dispose();
11854
- void runTransfer(target, files);
11855
- draw();
11856
- },
11857
- onCancel: () => {
11858
- agentSearchModal = null;
11859
- handle?.dispose();
11860
- draw();
11861
- },
11862
- });
11863
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
11864
- handle = display.pushModal(picker.surface);
11865
- agentSearchModal = picker;
11866
- draw();
11867
- }
11868
-
11869
11703
  async function runTransfer(
11870
11704
  target: TransferTarget,
11871
11705
  files: Array<{ localPath: string; size?: number }>,
@@ -11892,465 +11726,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11892
11726
  }
11893
11727
  }
11894
11728
 
11895
- /** T4-F2 / T4-F3 — open the Ctrl+T finder modal. Scans the
11896
- * current working dir (local only at MVP; remote scan is a
11897
- * follow-up). On accept, cd's the browser pane to the file's
11898
- * parent dir, focuses the file in the listing, and switches
11899
- * pane focus to browser so the user can e/enter next. */
11900
- async function openFinderPicker(): Promise<void> {
11901
- const targetBrowserId = activeBrowserWidgetId();
11902
- const targetBrowser = resolveBrowserStateById(targetBrowserId) ?? (workingDir as BrowserPaneModel);
11903
- // T5-I2 — branch on remote vs local. Both paths produce the
11904
- // same {absPath, relPath} finder items so the modal + onAccept
11905
- // handler stay unified.
11906
- let finderItems: FinderItem[] = [];
11907
- let backend = 'fd';
11908
- let truncated = false;
11909
- let scanDurationMs = 0;
11910
-
11911
- if (workingDir.remote) {
11912
- const host = workingDir.remote.host;
11913
- const remoteRoot = workingDir.remote.cwd;
11914
- finderFeedbackRuntime.onFinderScanStarted(`${host.name}:${remoteRoot}`);
11915
- draw();
11916
- const scan = await scanRemoteFinder({ host, root: remoteRoot });
11917
- const rels = relativizeRemoteResults(scan.paths, remoteRoot);
11918
- finderItems = scan.paths.map((absPath, i) => ({
11919
- absPath, relPath: rels[i] ?? absPath,
11920
- }));
11921
- backend = `${scan.backend}@${scan.host}`;
11922
- truncated = scan.truncated;
11923
- scanDurationMs = scan.durationMs;
11924
- } else {
11925
- // 2026-05-03 — scan from the Session Working Directory (the
11926
- // "project root"), NOT the browser pane's current navigation
11927
- // cursor. SWD is mutable only via Ctrl+W / /wd / SetWorkingDir
11928
- // tool — exactly the user's intent for "project root". Browser
11929
- // pane's cwd is purely UI navigation and should not narrow
11930
- // Ctrl+T's search scope (matches working-dir.ts:5-8 contract).
11931
- const scanRoot = getSessionCwd();
11932
- finderFeedbackRuntime.onFinderScanStarted(scanRoot);
11933
- draw();
11934
- const scan = await scanFinder({ root: scanRoot });
11935
- const rels = relativizeResults(scan.paths, scanRoot);
11936
- finderItems = scan.paths.map((absPath, i) => ({
11937
- absPath, relPath: rels[i] ?? absPath,
11938
- }));
11939
- backend = scan.backend;
11940
- truncated = scan.truncated;
11941
- scanDurationMs = scan.durationMs;
11942
- }
11943
- if (finderItems.length === 0) {
11944
- finderFeedbackRuntime.onFinderScanEmpty();
11945
- draw();
11946
- return;
11947
- }
11948
- finderFeedbackRuntime.onFinderScanCompleted({
11949
- count: finderItems.length,
11950
- backend,
11951
- truncated,
11952
- durationMs: scanDurationMs,
11953
- });
11954
-
11955
- const { cols, rows } = termSize();
11956
- const width = Math.min(90, Math.max(50, cols - 6));
11957
- const visibleRows = Math.min(15, Math.max(6, rows - 8));
11958
- const height = visibleRows + 3;
11959
- const bounds = {
11960
- row: Math.max(1, Math.floor((rows - height) / 2)),
11961
- col: Math.max(1, Math.floor((cols - width) / 2)),
11962
- width,
11963
- height,
11964
- };
11965
- let handle: ReturnType<typeof display.pushModal> | null = null;
11966
- // Title reflects the actual scan root — SWD for local (project
11967
- // root), remote cwd for remote mode. Browser pane's nav cursor
11968
- // is intentionally NOT shown because the scan ignores it.
11969
- const titleRoot = workingDir.remote
11970
- ? `${workingDir.remote.host.name}:${workingDir.remote.cwd}`
11971
- : getSessionCwd().replace(homedir(), '~');
11972
- // 2026-05-03 — preview modes (yazi/fzf-style, on-demand).
11973
- //
11974
- // Default mode is OFF — the picker opens cleanly with NO preview
11975
- // so it's never annoying on tablet / narrow terminals. The user
11976
- // toggles preview behaviour with `Ctrl+/` (matches zshrc
11977
- // `--bind 'ctrl-/:toggle-preview'`):
11978
- //
11979
- // off → hover → 2pane → off (cycle)
11980
- //
11981
- // • off — no preview, ever. The cursor moves freely.
11982
- // • hover — popup appears after the cursor stays still for
11983
- // 1 second (HOVER_DELAY_MS). Popup auto-dismisses
11984
- // after 4s. Centered, the same width as legacy
11985
- // behaviour but on-demand.
11986
- // • 2pane — browser-preview style: persistent preview popup
11987
- // docked to the right of the picker. Updates
11988
- // instantly as the cursor moves. Falls back to
11989
- // hover if the terminal is too narrow (<140 cols).
11990
- //
11991
- // showPreviewModal supports remote-skip by itself (preview
11992
- // handlers read local fs), so we simply guard with workingDir
11993
- // .remote before scheduling.
11994
- type PreviewMode = 'off' | 'hover' | '2pane';
11995
- const HOVER_DELAY_MS = 1000;
11996
- const TWO_PANE_MIN_COLS = 140;
11997
- const TWO_PANE_GROUP = 'finder-2pane-preview';
11998
-
11999
- let previewMode: PreviewMode = 'off';
12000
- let lastSelected: FinderItem | null = null;
12001
- let hoverTimer: ReturnType<typeof setTimeout> | null = null;
12002
- let lastPreviewHandle: { dispose(): void } | null = null;
12003
-
12004
- const clearHoverTimer = () => {
12005
- if (hoverTimer !== null) { clearTimeout(hoverTimer); hoverTimer = null; }
12006
- };
12007
- const disposeActivePreview = () => {
12008
- if (lastPreviewHandle) {
12009
- try { lastPreviewHandle.dispose(); } catch { /* ignore */ }
12010
- lastPreviewHandle = null;
12011
- }
12012
- };
12013
-
12014
- /** Compute the docked-preview bounds for 2-pane mode. Picker
12015
- * centred at width≈90; preview takes the right gutter. Returns
12016
- * null when there isn't enough room (caller falls back to
12017
- * hover). */
12018
- const computeDockedPreviewBounds = (): {
12019
- row: number; col: number; width: number; height: number;
12020
- } | null => {
12021
- const { cols: tc, rows: tr } = termSize();
12022
- if (tc < TWO_PANE_MIN_COLS) return null;
12023
- const pickerWidth = Math.min(90, Math.max(50, tc - 6));
12024
- const pickerCol = Math.max(1, Math.floor((tc - pickerWidth) / 2));
12025
- const previewCol = pickerCol + pickerWidth + 2;
12026
- const previewWidth = Math.max(40, tc - previewCol - 2);
12027
- if (previewWidth < 40) return null;
12028
- const previewHeight = Math.max(8, Math.min(tr - 6, 28));
12029
- const previewRow = Math.max(1, Math.floor((tr - previewHeight) / 2));
12030
- return { row: previewRow, col: previewCol, width: previewWidth, height: previewHeight };
12031
- };
12032
-
12033
- const paintPreviewFor = (item: FinderItem) => {
12034
- if (workingDir.remote) return; // remote previews unsupported
12035
- const { rows: tr, cols: tc } = termSize();
12036
- const docked = previewMode === '2pane' ? computeDockedPreviewBounds() : null;
12037
- void showPreviewModal(item.absPath, {
12038
- coordinator: display,
12039
- termCols: tc,
12040
- termRows: tr,
12041
- title: item.relPath,
12042
- ttlMs: previewMode === '2pane' ? 0 : 4000,
12043
- bounds: docked ?? undefined,
12044
- group: previewMode === '2pane' ? TWO_PANE_GROUP : 'image-preview',
12045
- }).then((r) => {
12046
- lastPreviewHandle = r?.handle ?? null;
12047
- }).catch(() => { /* preview failure is non-fatal */ });
12048
- };
12049
-
12050
- const scheduleHoverPreview = (item: FinderItem | null) => {
12051
- lastSelected = item;
12052
- clearHoverTimer();
12053
- if (previewMode === 'off') return;
12054
- if (!item || workingDir.remote) {
12055
- // Off-screen / remote — kill any straggling popup so the
12056
- // user doesn't see a preview that no longer matches the
12057
- // selection.
12058
- disposeActivePreview();
12059
- return;
12060
- }
12061
- if (previewMode === '2pane') {
12062
- // Instant update — no debounce. Persistent popup replaces
12063
- // itself via group dedup.
12064
- paintPreviewFor(item);
12065
- return;
12066
- }
12067
- // HOVER mode. Selection just changed → the popup currently on
12068
- // screen (if any) is stale. Drop it immediately so the user
12069
- // doesn't keep seeing the previous file's preview while their
12070
- // cursor is on a new file. The next paint waits HOVER_DELAY_MS
12071
- // of stillness before re-appearing.
12072
- disposeActivePreview();
12073
- hoverTimer = setTimeout(() => {
12074
- hoverTimer = null;
12075
- paintPreviewFor(item);
12076
- }, HOVER_DELAY_MS);
12077
- };
12078
-
12079
- /** Cycle off → hover → 2pane → off. Surfaces a status line so
12080
- * the user knows which mode they're in (Ctrl+/ has no chrome
12081
- * feedback otherwise). 2pane on a narrow terminal degrades
12082
- * to hover with a warning. */
12083
- const cyclePreviewMode = (): void => {
12084
- const next: PreviewMode = previewMode === 'off' ? 'hover'
12085
- : previewMode === 'hover' ? '2pane'
12086
- : 'off';
12087
- previewMode = next;
12088
- clearHoverTimer();
12089
- // Drop the active popup whenever the mode changes. The new
12090
- // mode (if any) re-paints below.
12091
- disposeActivePreview();
12092
-
12093
- if (next === 'off') {
12094
- chatLines.push(C.muted(' finder preview: OFF (Ctrl+/ to cycle modes)'));
12095
- } else if (next === 'hover') {
12096
- chatLines.push(C.muted(' finder preview: HOVER (popup after 1 s · Ctrl+/ next)'));
12097
- } else {
12098
- // 2pane — refuse if the terminal is too narrow; degrade to
12099
- // hover so the user gets *some* preview without overlap.
12100
- const docked = computeDockedPreviewBounds();
12101
- if (!docked) {
12102
- previewMode = 'hover';
12103
- chatLines.push(C.warning(` finder preview: 2-pane needs ≥${TWO_PANE_MIN_COLS} cols — falling back to HOVER`));
12104
- } else {
12105
- chatLines.push(C.muted(' finder preview: 2-PANE (docked right · Ctrl+/ to disable)'));
12106
- if (lastSelected) paintPreviewFor(lastSelected);
12107
- }
12108
- }
12109
- chatScrollOffset = -1;
12110
- draw();
12111
- };
12112
-
12113
- // Declared up here so `onAccept`/`onCancel` closures below can
12114
- // reference both; assigned right after the picker mounts.
12115
- let previewToggleDisposable: { dispose(): void } | null = null;
12116
- let previewDismissDisposable: { dispose(): void } | null = null;
12117
- const picker = createFinderModal({
12118
- items: finderItems,
12119
- bounds, width, maxVisible: visibleRows,
12120
- truncated,
12121
- title: `Find file in ${titleRoot}`,
12122
- theme: currentThemeTokens(),
12123
- onHover: scheduleHoverPreview,
12124
- onAccept: (item) => {
12125
- clearHoverTimer();
12126
- disposeActivePreview();
12127
- previewToggleDisposable?.dispose();
12128
- previewDismissDisposable?.dispose();
12129
- agentSearchModal = null;
12130
- handle?.dispose();
12131
- const parentDir = item.absPath.slice(0, item.absPath.lastIndexOf('/')) || '/';
12132
- const basename = item.absPath.slice(item.absPath.lastIndexOf('/') + 1);
12133
- if (workingDir.remote) {
12134
- // T5-I2 — remote cd + focus-by-basename.
12135
- enterRemoteDirectory(workingDir, parentDir);
12136
- (async () => {
12137
- await refreshRemoteWorkingDir(workingDir, {}, dockedPreview);
12138
- const idx = workingDir.entries.findIndex(e => e.name === basename);
12139
- if (idx >= 0) {
12140
- workingDir.cursor = idx;
12141
- workingDir.offset = Math.max(0, idx - 3);
12142
- }
12143
- await refreshRemotePreviewBridge();
12144
- pushDebugLine(C.muted(` ${workingDir.remote?.host.name}:${item.relPath}`));
12145
- chatScrollOffset = -1;
12146
- draw();
12147
- })().catch(() => {});
12148
- } else {
12149
- enterDirectory(targetBrowser as WorkingDirState, parentDir);
12150
- refreshWorkingDir(targetBrowser as WorkingDirState);
12151
- const idx = targetBrowser.entries.findIndex(e => e.name === basename);
12152
- if (idx >= 0) {
12153
- targetBrowser.cursor = idx;
12154
- targetBrowser.offset = Math.max(0, idx - 3);
12155
- }
12156
- refreshWorkingDirPreview();
12157
- pushDebugLine(C.muted(` ${item.relPath}`));
12158
- chatScrollOffset = -1;
12159
- }
12160
- setWorkingFocus('browser', 'agent-roster-search-pick');
12161
- draw();
12162
- },
12163
- onCancel: () => {
12164
- clearHoverTimer();
12165
- disposeActivePreview();
12166
- previewToggleDisposable?.dispose();
12167
- previewDismissDisposable?.dispose();
12168
- agentSearchModal = null;
12169
- handle?.dispose();
12170
- draw();
12171
- },
12172
- });
12173
- // Ctrl+/ (and Ctrl+_ which some terminals send for the same
12174
- // physical chord) cycles preview modes. Active for the lifetime
12175
- // of THIS picker only — disposed in both onAccept and onCancel
12176
- // above. Q4 (substrate Occam): no per-binding pipe alias, register
12177
- // each variant separately. (Korean IME aliases unnecessary — Ctrl+/
12178
- // has no jamo mapping on the 2-set layout.)
12179
- const togglePreviewMode = (): void => { cyclePreviewMode(); };
12180
- const previewSlash = display.registerKeyBinding({
12181
- id: `dashboard:finder-picker-preview-toggle-slash:${Date.now().toString(36)}`,
12182
- key: 'C-/',
12183
- scope: 'global',
12184
- when: () => agentSearchModal === picker,
12185
- handler: togglePreviewMode,
12186
- });
12187
- const previewUnder = display.registerKeyBinding({
12188
- id: `dashboard:finder-picker-preview-toggle-under:${Date.now().toString(36)}`,
12189
- key: 'C-_',
12190
- scope: 'global',
12191
- when: () => agentSearchModal === picker,
12192
- handler: togglePreviewMode,
12193
- });
12194
- previewToggleDisposable = {
12195
- dispose: () => { previewSlash.dispose(); previewUnder.dispose(); },
12196
- };
12197
- // Ctrl+] — instantly dismiss the active preview popup WITHOUT
12198
- // closing the picker or changing the mode. Esc would be more
12199
- // intuitive but the picker captures Esc internally to cancel
12200
- // itself. Ctrl+\ is unsafe because it raises SIGQUIT in many
12201
- // terminal drivers. Ctrl+] (historically "telnet escape" =
12202
- // "exit this context") is universally safe + semantically right.
12203
- // Active only while a preview is currently visible — otherwise
12204
- // the binding silently no-ops (no flicker, no log spam).
12205
- // Mode stays as set; if user is in HOVER mode, the next
12206
- // cursor stillness re-triggers the popup. To stop the popup
12207
- // from re-appearing entirely, cycle Ctrl+/ back to OFF.
12208
- previewDismissDisposable = display.registerKeyBinding({
12209
- id: `dashboard:finder-picker-preview-dismiss:${Date.now().toString(36)}`,
12210
- key: 'C-]',
12211
- scope: 'global',
12212
- when: () => agentSearchModal === picker && lastPreviewHandle !== null,
12213
- handler: () => {
12214
- clearHoverTimer();
12215
- disposeActivePreview();
12216
- draw();
12217
- },
12218
- });
12219
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12220
- handle = display.pushModal(picker.surface);
12221
- agentSearchModal = picker;
12222
- draw();
12223
- }
12224
-
12225
- /** Yazi-style Alt+C — directory finder. Symmetric with openFinder
12226
- * Picker but enumerates directories only (`scanFinder kind:'dir'`)
12227
- * and on accept cd's the browser pane to the chosen directory.
12228
- * Remote-mode is currently unsupported (surfaces a warning) — the
12229
- * remote finder would need a parallel kind-aware backend. */
12230
- async function openDirectoryPicker(): Promise<void> {
12231
- if (workingDir.remote) {
12232
- chatLines.push(C.warning(' Alt+C: directory finder is local-only — Esc remote first.'));
12233
- chatScrollOffset = -1;
12234
- draw();
12235
- return;
12236
- }
12237
- const targetBrowserId = activeBrowserWidgetId();
12238
- const targetBrowser = resolveBrowserStateById(targetBrowserId) ?? (workingDir as BrowserPaneModel);
12239
- // Symmetric with openFinderPicker — scan from SWD (project root),
12240
- // not the browser pane's nav cursor. The accept handler still
12241
- // mutates targetBrowser to navigate into the chosen dir.
12242
- const scanRoot = getSessionCwd();
12243
- finderFeedbackRuntime.onFinderScanStarted(`${scanRoot} (dirs)`);
12244
- draw();
12245
- const scan = await scanFinder({ root: scanRoot, kind: 'dir' });
12246
- const rels = relativizeResults(scan.paths, scanRoot);
12247
- const dirItems: FinderItem[] = scan.paths.map((absPath, i) => ({
12248
- absPath, relPath: rels[i] ?? absPath,
12249
- }));
12250
- if (dirItems.length === 0) {
12251
- finderFeedbackRuntime.onFinderScanEmpty();
12252
- draw();
12253
- return;
12254
- }
12255
- finderFeedbackRuntime.onFinderScanCompleted({
12256
- count: dirItems.length,
12257
- backend: scan.backend,
12258
- truncated: scan.truncated,
12259
- durationMs: scan.durationMs,
12260
- });
12261
-
12262
- const { cols, rows } = termSize();
12263
- const width = Math.min(90, Math.max(50, cols - 6));
12264
- const visibleRows = Math.min(15, Math.max(6, rows - 8));
12265
- const height = visibleRows + 3;
12266
- const bounds = {
12267
- row: Math.max(1, Math.floor((rows - height) / 2)),
12268
- col: Math.max(1, Math.floor((cols - width) / 2)),
12269
- width,
12270
- height,
12271
- };
12272
- let handle: ReturnType<typeof display.pushModal> | null = null;
12273
- const titleRoot = scanRoot.replace(homedir(), '~');
12274
- const picker = createFinderModal({
12275
- items: dirItems,
12276
- bounds, width, maxVisible: visibleRows,
12277
- truncated: scan.truncated,
12278
- title: `Find directory in ${titleRoot}`,
12279
- theme: currentThemeTokens(),
12280
- onAccept: (item) => {
12281
- agentSearchModal = null;
12282
- handle?.dispose();
12283
- enterDirectory(targetBrowser as WorkingDirState, item.absPath);
12284
- refreshWorkingDir(targetBrowser as WorkingDirState);
12285
- refreshWorkingDirPreview();
12286
- pushDebugLine(C.muted(` cd ${item.relPath}`));
12287
- chatScrollOffset = -1;
12288
- setWorkingFocus('browser', 'alt-c-dir-pick');
12289
- draw();
12290
- },
12291
- onCancel: () => {
12292
- agentSearchModal = null;
12293
- handle?.dispose();
12294
- draw();
12295
- },
12296
- });
12297
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12298
- handle = display.pushModal(picker.surface);
12299
- agentSearchModal = picker;
12300
- draw();
12301
- }
12302
-
12303
- /** T4-E4 — open the SSH host picker (Ctrl+K). Accept connects
12304
- * the browser pane to the remote host; Esc cancels silently.
12305
- * Routes keys through agentSearchModal (shared pattern with
12306
- * window + session pickers). */
12307
- function openSshPicker(): void {
12308
- const { cols, rows } = termSize();
12309
- const width = Math.min(72, Math.max(44, cols - 6));
12310
- const visibleRows = 8;
12311
- const height = visibleRows + 3;
12312
- const bounds = {
12313
- row: Math.max(1, Math.floor((rows - height) / 2)),
12314
- col: Math.max(1, Math.floor((cols - width) / 2)),
12315
- width,
12316
- height,
12317
- };
12318
- let handle: ReturnType<typeof display.pushModal> | null = null;
12319
- const picker = createSshPickerModal({
12320
- bounds, width, maxVisible: visibleRows,
12321
- activeHostName: workingDir.remote?.host.name ?? null,
12322
- theme: currentThemeTokens(),
12323
- onAccept: async (host) => {
12324
- agentSearchModal = null;
12325
- handle?.dispose();
12326
- touchSshHost(host.name);
12327
- startRemoteModeBridge(host, '.');
12328
- finderFeedbackRuntime.onSshConnectStarted({ name: host.name, host: host.host });
12329
- draw();
12330
- try {
12331
- await refreshRemoteWorkingDir(workingDir, {}, dockedPreview);
12332
- await refreshRemotePreviewBridge();
12333
- finderFeedbackRuntime.onSshConnectSucceeded(host.name);
12334
- } catch (err) {
12335
- finderFeedbackRuntime.onSshConnectFailed(
12336
- host.name,
12337
- err instanceof Error ? err.message : String(err),
12338
- );
12339
- }
12340
- draw();
12341
- },
12342
- onCancel: () => {
12343
- agentSearchModal = null;
12344
- handle?.dispose();
12345
- draw();
12346
- },
12347
- });
12348
- attachSurfaceToWorkspace(picker.surface, currentWorkspaceOwnerId());
12349
- handle = display.pushModal(picker.surface);
12350
- agentSearchModal = picker;
12351
- draw();
12352
- }
12353
-
12354
11729
  // FU-1 — register ssh-picker / finder-picker openers as display
12355
11730
  // key bindings. Moves the dashboard hard branches onto the same
12356
11731
  // coordinator dispatch path the rest of the modals / bindings use.
@@ -12811,43 +12186,6 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
12811
12186
  onContextMenuMouse: (ev) => ctxMenuWire.onMouse(ev),
12812
12187
  });
12813
12188
  };
12814
- const openBrowserOnlyModal = (): void => {
12815
- const { cols: tc, rows: tr } = termSize();
12816
- const liveMode = resolveBrowserPreviewModalLiveMode(process.env.ELANOUS_PANE_MODAL_LIVE);
12817
- void openBrowserPaneModal({
12818
- browserWidgetInstanceId: browserWidgetInstanceIdForView(workingDir.view),
12819
- liveMode,
12820
- browserPaneRegistry,
12821
- widgetHost,
12822
- coordinator: display,
12823
- termCols: tc,
12824
- termRows: tr,
12825
- captureSnapshot: () => captureDeferredPane('browser'),
12826
- theme: currentThemeTokens(),
12827
- fmtEntryColored,
12828
- iconForEntry,
12829
- onSubmit: dispatchSidebarSubmit,
12830
- onDispose: () => { draw(); },
12831
- onCancel: () => { draw(); },
12832
- });
12833
- };
12834
- const openPreviewOnlyModal = (): void => {
12835
- const { cols: tc, rows: tr } = termSize();
12836
- const liveMode = (process.env.ELANOUS_PANE_MODAL_LIVE ?? 'on').toLowerCase() !== 'off';
12837
- void openPreviewPaneModal({
12838
- preview: dockedPreview,
12839
- previewWidgetInstanceId: 'wd-preview',
12840
- liveMode,
12841
- widgetHost,
12842
- coordinator: display,
12843
- termCols: tc,
12844
- termRows: tr,
12845
- captureSnapshot: () => captureDeferredPane('preview'),
12846
- theme: currentThemeTokens(),
12847
- onDispose: () => { draw(); },
12848
- onCancel: () => { draw(); },
12849
- });
12850
- };
12851
12189
  registerDashboardGlobalKeys({
12852
12190
  enableSupplementalGlobalKeys: getUserConfig().dashboard.enableSupplementalGlobalKeys,
12853
12191
  register: (binding) => { display.registerKeyBinding(binding); },
@@ -13071,6 +12409,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13071
12409
  repaint: () => void;
13072
12410
  insertAtCursor?: (text: string) => void;
13073
12411
  submit?: (request?: string | TextInputExternalSubmitRequest) => void;
12412
+ getBufferText?: () => string;
13074
12413
  } = { repaint: () => {} };
13075
12414
  repaintPromptAfterRender = () => {
13076
12415
  if (shouldSuppressDashboardBottomArea()) {
@@ -13094,6 +12433,14 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13094
12433
  // safe. Dispose on process exit to keep the listener set clean.
13095
12434
  const disposeResizeListener = installResizeListener({ draw: (opts) => draw(opts), promptCtl });
13096
12435
  process.on('exit', disposeResizeListener);
12436
+ // 외부 주입(`pty text`)을 사용자 타이핑과 섞지 않고 별도 대기열로 — 결함 #6(10-14).
12437
+ const disposeExternalInputHost = registerExternalInputHost(createDashboardExternalInputHost({
12438
+ isTurnActive: () => streamingInFlight,
12439
+ getTurnStop: () => currentTurnExternalStop,
12440
+ promptCtl,
12441
+ pushNotice: (line) => { chatLines.push(C.muted(` ${line}`)); chatScrollOffset = -1; draw(); },
12442
+ }));
12443
+ process.on('exit', disposeExternalInputHost);
13097
12444
 
13098
12445
  const publishScratchSurface = (
13099
12446
  source: string,
@@ -13112,6 +12459,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13112
12459
  const detailViewerRuntime = createDashboardDetailViewerRuntime({
13113
12460
  setOpen: (open) => { setCompanionPopupOpen('detail', open); },
13114
12461
  requestRender: () => { requestDashboardRender('detail'); },
12462
+ // 채팅 단독 화면에선 그리드 투영이 안 돌아 팝업이 «(no preview content)» 였다 — 그리기 전에 싣는다.
12463
+ project: (snapshot) => {
12464
+ const detailWidget = widgetHost.get('wd-detail') as WidgetInstance<Record<string, unknown>> | null;
12465
+ if (detailWidget?.state && typeof detailWidget.state === 'object') {
12466
+ companionWidgetRuntime.projectDetailWidget(detailWidget, snapshot);
12467
+ }
12468
+ },
13115
12469
  });
13116
12470
  const companionWidgetRuntime = createDashboardCompanionWidgetRuntime();
13117
12471
  const clipboardHistoryRuntime = createDashboardClipboardHistoryRuntime();
@@ -13801,6 +13155,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13801
13155
  let session: TerminalSession;
13802
13156
  try {
13803
13157
  if (req.kind === 'shell') {
13158
+ const shellSeat = resolveTuiShellSeat(req.cwd, process.env.COORD_ID);
13804
13159
  session = sessionRegistry.spawn(
13805
13160
  {
13806
13161
  title: 'terminal',
@@ -13808,6 +13163,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13808
13163
  command: req.command,
13809
13164
  env: req.env,
13810
13165
  kind: 'shell',
13166
+ useRegistry: true,
13167
+ registryKind: 'shell',
13168
+ registryNickname: `tui-shell:${req.cwd}`,
13169
+ registryOrigin: { originRoot: 'elanous-internal', controller: 'tui-shell' },
13170
+ registryPurpose: `tui-shell:${shellSeat}`,
13171
+ registrySeat: shellSeat,
13811
13172
  onExit: handleExit,
13812
13173
  onTitleAction: handleTitleAction,
13813
13174
  },
@@ -13899,8 +13260,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
13899
13260
  openInteractiveTerminalPopup,
13900
13261
  });
13901
13262
  const TerminalPopup = terminalPopupRuntime;
13902
- const spawnGlobalTerminalModal = (): void => {
13903
- terminalPopupRuntime.spawnGlobalTerminalModal(workingDir.cwd);
13263
+ const spawnGlobalTerminalModal = (command?: string): void => {
13264
+ openInteractiveTerminalPopup({ kind: 'shell', cwd: workingDir.cwd, command });
13904
13265
  };
13905
13266
 
13906
13267
  // E3 (2026-05-17) — copyLastAssistantTurnToClipboard /
@@ -14022,7 +13383,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
14022
13383
  },
14023
13384
  toggleLogZoom: () => { toggleChatOnlyLayout(); },
14024
13385
  copyLastBlock: copyLastAssistantTurnToClipboard,
14025
- spawnTerminalModal: spawnGlobalTerminalModal,
13386
+ spawnTerminalModal: () => spawnGlobalTerminalModal(),
14026
13387
  copyLogPane: copyLogPaneToClipboard,
14027
13388
  // Ctrl+L — 터미널 클리어 후 전체 강제 리페인트(resize 리스너와 동일 레시피).
14028
13389
  // 외부 출력/아티팩트로 더러워진 화면 복구용 · 대화(chatLines)는 보존.
@@ -14091,6 +13452,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
14091
13452
  },
14092
13453
  onPreviewTerminalExpandChanged: chordFeedbackRuntime.onPreviewTerminalExpandChanged,
14093
13454
  onMissingPreviewTerminal: chordFeedbackRuntime.onMissingPreviewTerminal,
13455
+ // #19 — Ctrl+X f from a pane; the input path is the `ctrl+x f` input-core binding.
13456
+ toggleAllFolds: toggleAllLogFolds,
14094
13457
  });
14095
13458
 
14096
13459
  // ══════════════════════════════════════════════════════════════
@@ -15369,6 +14732,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15369
14732
  // adds turn concurrency must key this by turn (or session+turn) first.
15370
14733
  let acpTurnToolSpecs: LLMToolSpec[] = [];
15371
14734
  let acpDispatchToolSelf: CoreTurnDispatchTool | null = null;
14735
+ let acpToolDispatchCount = 0;
15372
14736
  const trMod = await import('../tool-runtime/index.js');
15373
14737
  trMod.registerAllDefaultToolRuntimes();
15374
14738
  acpToolRuntime = trMod;
@@ -15423,6 +14787,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15423
14787
  modelFamily: getModelFamily(inspectActiveProvider().model),
15424
14788
  rich: false,
15425
14789
  enabledTools: tools.map(t => t.name),
14790
+ seatBoot: true,
15426
14791
  }),
15427
14792
  ];
15428
14793
  },
@@ -15452,35 +14817,16 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15452
14817
  // Codex-first decision as every other surface. The core bridge now
15453
14818
  // supplies userText to resolveModel, preserving explicit pins and
15454
14819
  // making this the actual model override rather than a display hint.
15455
- getActiveModel: ({ userText }) => {
15456
- const cfg = getUserConfig();
15457
- const decision = resolveRouteDecision({
15458
- provider: cfg.llm.provider,
15459
- configuredModel: inspectActiveProvider().model,
15460
- text: userText,
15461
- routePolicy: cfg.llm.routePolicy,
15462
- });
15463
- // Badge truthfulness(2026-07-19 goal-exec) — resolveRouteDecision 의 codex
15464
- // tier-policy 브랜치는 curated effort(예: balanced=low)를 찍지만, codex provider 는
15465
- // 그 값을 wire 로 보내지 않는다: 실제 effort 는 effectiveReasoningLevel(cfg)(config
15466
- // 주도, reasoningLevel 미설정 시 model-default high). 그대로 두면 완료 뱃지가
15467
- // "terra(low)" 로 떠 사용자가 모델이 약하게 돈다고 오해한다(실제 high). 실제 wire
15468
- // effort 로 덮어써 뱃지를 진실하게. decision.effort 는 표시 전용이라 라우팅 무영향.
15469
- const trueEffort = effectiveReasoningLevel(cfg.llm, decision.provider, decision.model);
15470
- const badgedDecision = trueEffort && trueEffort !== 'off'
15471
- ? { ...decision, effort: trueEffort }
15472
- : decision;
15473
- recordCurrentRouteDecision('dashboard', badgedDecision);
15474
- if (debug.enabled) debug.log('llm.route-decision', 'dashboard-acp', {
15475
- provider: decision.provider, model: decision.model, mission: decision.mission, source: decision.source,
15476
- });
15477
- return decision.model || null;
15478
- },
14820
+ // 본체 = acp-active-model.ts(실물 경로를 시험이 그대로 부르도록 뺐다 · TUI-MODEL-SESSION-SCOPE).
14821
+ getActiveModel: ({ userText }) => resolveDashboardActiveModel(userText),
15479
14822
  // ⭐ The assignment is deliberate, not a typo: it captures a reference to
15480
14823
  // this very dispatcher so `agentDispatchTool` below can route a
15481
14824
  // sub-agent's tool calls back through it. A plain named const would need
15482
14825
  // the closure hoisted out of this literal; this keeps the diff local.
15483
14826
  dispatchTool: acpDispatchToolSelf = async (name, args, ctx) => {
14827
+ // Count at the server-side boundary, before any tool effect and
14828
+ // before the ACP notification can reach the TUI client.
14829
+ acpToolDispatchCount++;
15484
14830
  const runtime = acpToolRuntime;
15485
14831
  const persist = acpPersistToolOutputPreview;
15486
14832
  if (!runtime || !persist) {
@@ -15624,8 +14970,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15624
14970
  // — the boot fell back to a fresh session. Surface the reason
15625
14971
  // so the user knows they're on a different session than asked.
15626
14972
  // - new (no resume requested): nothing extra to render.
14973
+ // 대표 #73 — 인자 없는 기동 = 새 세션. 이어받았는지는 이 값 하나로 판정한다(관측 ⊕ 자리 부트 카드).
14974
+ let tuiBootSessionStart: { origin: 'new' | 'resume'; reason: string; restoredSessionId?: string } = opts.resumeSessionId
14975
+ ? { origin: 'new', reason: 'resume-failed' }
14976
+ : { origin: 'new', reason: 'default-new' };
15627
14977
  if (dashboardAcpBootResult.mode === 'resumed' && dashboardAcpBootResult.sessionId) {
15628
14978
  const sid = dashboardAcpBootResult.sessionId;
14979
+ tuiBootSessionStart = { origin: 'resume', reason: 'resume-daemon', restoredSessionId: sid };
15629
14980
  chatLines.push(C.success(` ✓ resumed daemon session: ${sid}`));
15630
14981
  // Best-effort REST history replay over the remote HTTP/WS endpoint.
15631
14982
  if (opts.remote) {
@@ -15687,10 +15038,27 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15687
15038
  if (!resume.resumed) {
15688
15039
  chatLines.push(C.warning(` ⚠ resume failed (${resume.reason}); started new session`));
15689
15040
  } else {
15041
+ tuiBootSessionStart = {
15042
+ origin: 'resume',
15043
+ reason: resume.repaired ? 'resume-local-repaired' : 'resume-local',
15044
+ restoredSessionId: resume.sessionId,
15045
+ };
15690
15046
  chatLines.push(C.success(` ✓ resumed local session: ${resume.sessionId}`));
15047
+ if (resume.repaired) {
15048
+ chatLines.push(C.muted(` 깨진 도구 호출·결과 짝을 잘라 이어받았다(호출 ${resume.repaired.removedCalls} · 결과 ${resume.repaired.removedResults})`));
15049
+ }
15691
15050
  chatLines.push(C.muted(` ${resume.turns} turns restored. Next turn continues this conversation.`));
15692
15051
  }
15693
15052
  }
15053
+ observeSessionStart({
15054
+ surface: 'tui',
15055
+ origin: tuiBootSessionStart.origin,
15056
+ reason: tuiBootSessionStart.reason,
15057
+ // sessionId = 이 대화가 이어받은(또는 새로 연) 대화 기록의 id · acpSessionId = 백엔드 ACP 세션 id(둘이 다를 수 있다).
15058
+ sessionId: tuiBootSessionStart.restoredSessionId ?? dashboardAcpSession.currentSessionId ?? null,
15059
+ acpSessionId: dashboardAcpSession.currentSessionId ?? null,
15060
+ resumeRequested: opts.resumeSessionId !== undefined,
15061
+ });
15694
15062
  // 제안은 세션 작업 폴더 기준 — 시작 폴더 한 번이 아니라 폴더가 바뀔 때마다 다시 계산한다(IA1 리뷰 must-fix).
15695
15063
  const nextProjectSuggestion = createProjectSuggester(suggestProjectForFolder);
15696
15064
  const pushProjectSuggestion = (folder: string): void => {
@@ -15702,16 +15070,37 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15702
15070
  }
15703
15071
  subscribeSessionCwd((state) => { pushProjectSuggestion(state.cwd); });
15704
15072
  chatScrollOffset = -1;
15705
- void renderDashboardFirstScreenBand({
15073
+ const firstScreenBandInput = {
15706
15074
  history: chat.history,
15707
15075
  lines: chatLines,
15708
15076
  width: Math.max(0, termSize().cols - 4),
15709
15077
  // The band ships ko/en only; any other detected locale keeps the English rows.
15710
- locale: detectLocale() === 'ko' ? 'ko' : 'en',
15078
+ locale: detectLocale() === 'ko' ? 'ko' as const : 'en' as const,
15711
15079
  draw: () => draw(),
15712
- observe: (data) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
15713
- });
15080
+ observe: (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
15081
+ };
15082
+ void (bootBannerMode === 'full'
15083
+ ? renderDashboardFirstScreenBand(firstScreenBandInput)
15084
+ : renderMinimalBootBand({
15085
+ ...firstScreenBandInput,
15086
+ guidance: needsOnboarding(getUserConfig()),
15087
+ held: bootBannerHeld,
15088
+ show: () => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } }),
15089
+ daemonLink: resolveTuiDaemonLink,
15090
+ styleFailure: (line) => C.warning(line),
15091
+ observeHeld: (data) => { try { debug.log('dashboard.first-screen-band', 'held', data); } catch { /* observation is best-effort */ } },
15092
+ }));
15714
15093
 
15094
+ // 🪪 L1 — 자리 부트(seat-boot-preamble.ts): 자리면 첫 화면에 한 줄.
15095
+ // 🪪 L2-lite(seat-local-instructions.ts): 자리 로컬 지시가 없으면 템플릿 생성(덮어쓰기 0) ⊕ 대화 끝 «지난 세션» 기록.
15096
+ const seatLocal = ensureSeatLocalInstructions(getSessionCwd());
15097
+ if (seatLocal.status === 'created') chatLines.push(C.muted(` 📝 자리 로컬 지시를 만들었다 — ${seatLocal.path} (사람이 고쳐도 된다)`));
15098
+ else if (seatLocal.status === 'exists' && seatLocal.missingPrinciple) chatLines.push(C.muted(` 💡 ${seatLocal.path} 에 제1원칙 절이 없다 — 넣을지 사람이 정한다(덮어쓰지 않음)`));
15099
+ installSeatSessionRecorder({ getCwd: () => getSessionCwd(), getHistory: () => chat.history });
15100
+ const seatBoot = describeSeatBoot(getSessionCwd());
15101
+ if (seatBoot) chatLines.push(C.muted(` 🪪 ${seatBoot.seat} 자리 · 안 읽은 우편 ${seatBoot.unread ?? '미관측'} — 부트 카드를 턴마다 싣는다`));
15102
+ // 새 세션(이어받기 실패 포함)이면 자리 부트 카드로 시작한다 — 자리 맥락은 세션이 아니라 자리 문서가 싣는다.
15103
+ if (tuiBootSessionStart.origin === 'new') startSeatBootConversation({ reason: 'boot' });
15715
15104
  tuiSeatAskTimer = setInterval(pollTuiSeatAnswers, 10_000);
15716
15105
  tuiSeatAskTimer.unref();
15717
15106
  pollTuiSeatAnswers();
@@ -16696,7 +16085,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16696
16085
  sync.cursors[sync.focus] = Math.max(0, Math.min(sync.cursors[sync.focus]! + delta, sync.lists[sync.focus]!.length - 1));
16697
16086
  } else if (mr > logViewport.startRow && mr <= logViewport.endRow) {
16698
16087
  // Scroll the log pane
16699
- const maxScr = Math.max(0, chatLines.length - logViewport.height);
16088
+ const maxScr = logScrollMax(logViewport.height);
16700
16089
  chatScrollOffset = Math.max(0, Math.min((chatScrollOffset < 0 ? maxScr : chatScrollOffset) + delta, maxScr));
16701
16090
  if (delta > 0 && chatScrollOffset >= maxScr) chatScrollOffset = -1;
16702
16091
  }
@@ -16769,19 +16158,23 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16769
16158
  // 다음 inner-loop 반복이 남은 항목도 같은 순서로 제출한다.
16770
16159
  // ⭐ 드레인 순서 자체는 `chat/turn-typeahead.ts` 가 소유한다 — 여기서 베끼면
16771
16160
  // 테스트가 그 순서를 다시 베껴야 하고, 베낀 테스트는 배선 회귀를 못 잡는다.
16772
- const interrupted = lastTurnFinalStatus === 'interrupted';
16161
+ internalTurnGate.noteTurnSettled();
16162
+ // «지금 밀어 넣기»로 끊은 턴은 «중단»이 아니다 — 복원하지 않고 큐 머리를 곧바로 제출한다.
16163
+ const interrupted = consumeTurnEndInterrupted(lastTurnFinalStatus, steerInterruptRef);
16773
16164
  const queuedBeforeDrain = turnTypeaheadRef.state.queuedSubmissions.length;
16774
- const drained = drainTurnTypeaheadOnce(turnTypeaheadRef.state, nextInitial, { interrupted });
16165
+ // ⭐ 드레인 → 레지스트리 해제 → Enter 주입은 다리 모듈이 한 함수로 한다(시험이 같은 함수를 문다).
16166
+ const drained = handoffTurnTypeaheadAtTurnEnd(turnTypeaheadRef, nextInitial, { interrupted }, () => {
16167
+ injectKey({ name: 'enter', ctrl: false, shift: false });
16168
+ });
16775
16169
  lastTurnFinalStatus = undefined;
16776
- turnTypeaheadRef.state = drained.state;
16777
16170
  nextInitial = drained.nextInitial;
16778
16171
  const queuedTurnEndText = drained.injectEnter ? drained.nextInitial : undefined;
16779
16172
  if (interrupted && queuedBeforeDrain > 0) {
16780
16173
  debug.log('dashboard.turn-typeahead', 'restored-after-interrupt', { count: queuedBeforeDrain });
16781
16174
  }
16782
- if (drained.injectEnter) injectKey({ name: 'enter', ctrl: false, shift: false });
16783
16175
  draw();
16784
- const { cols: inputCols } = termSize();
16176
+ const getInputCols = () => termSize().cols;
16177
+ const inputCols = getInputCols();
16785
16178
  // Row is published by draw() via the `input-prompt` zone's
16786
16179
  // resolved geometry — no `termRows - N` math here anymore.
16787
16180
  // The composer is the single source of truth; if the layout
@@ -16792,6 +16185,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16792
16185
  promptFrame: currentPromptFrame(termSize().rows),
16793
16186
  getPromptFrame: () => currentPromptFrame(termSize().rows),
16794
16187
  inputCols,
16188
+ getInputCols,
16795
16189
  surfaceRegistry: getSurfaceRegistry(),
16796
16190
  invalidateRenderCacheRow,
16797
16191
  buildPromptFrameDividerRows,
@@ -16805,11 +16199,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16805
16199
  paintFrameNow: () => drawNow(),
16806
16200
  preferHistoryArrowKeys: slashQuickMode,
16807
16201
  history: inputHistory,
16202
+ // TUI-INPUT-PLACEHOLDER-OFF — provider 가 있으면 빈 입력창은 비워 둔다(대표 «불필요한 가이드 없애라»).
16808
16203
  placeholder: anyProviderAvailable()
16809
- ? '말 한 줄로 시작 — 질문하거나 «하니스로 구현 …» · /command · /path/to.pdf 첨부'
16204
+ ? ''
16810
16205
  : '/quit /clear (run `elanous setup` — no LLM provider available)',
16811
16206
  debugLog: debug.enabled ? (event, label, payload) => debug.log(event, label, payload) : undefined,
16812
16207
  textInputOpts: {
16208
+ internalTurnYield: internalTurnYieldPort,
16813
16209
  onScrollKey: (name) => {
16814
16210
  const { rows } = termSize();
16815
16211
  const viewport = computePromptFrameLogViewportBounds(
@@ -17251,6 +16647,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17251
16647
  }),
17252
16648
  },
17253
16649
  });
16650
+ // ⭐ H2 — 입력칸이 내부 턴 문에 «비켜 줬다». 꺼낸 요청을 같은 턴 실행 경로로 보낸다
16651
+ // (슬래시 해석·히스토리·사람 발화 에코 없음). 꺼낼 게 없으면 입력으로 되돌아간다.
16652
+ const consumed = consumeInternalTurnYield(internalTurnGate, input);
16653
+ if (consumed.kind === 'reenter') continue;
16654
+ const internalTurn: InternalTurnRequest | null = consumed.kind === 'internal' ? consumed.request : null;
16655
+ input = consumed.input;
17254
16656
 
17255
16657
  const postTurn = resolveDashboardChatMainPostTurn(input, {
17256
16658
  inputLines: dashboardState.getInputLines(),
@@ -17302,7 +16704,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17302
16704
 
17303
16705
  // Save to durable input history. Up/down reads the in-memory
17304
16706
  // tail, while slash/LLM tools can search the full store.
17305
- if (postTurn.historyRecord) {
16707
+ if (!internalTurn) internalTurnGate.noteHumanTurn();
16708
+ if (postTurn.historyRecord && !internalTurn) {
17306
16709
  inputHistoryStore.record(postTurn.historyRecord);
17307
16710
  refreshInputHistory();
17308
16711
  }
@@ -17460,14 +16863,16 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17460
16863
  // resource_link blocks so dropped screenshots and PDFs flow
17461
16864
  // naturally to the agent. debug.log gates the snapshot so
17462
16865
  // off-state stays zero-cost.
17463
- const submitIntent = resolveDashboardHarnessSubmitIntent(input.text, {
17464
- stickyBackend: dashboardAcpChat.getSticky(),
17465
- source: input.externalSubmitSource,
17466
- });
16866
+ const submitIntent = internalTurn
16867
+ ? resolveInternalTurnSubmitIntent(internalTurn, dashboardAcpSession.currentSessionId)
16868
+ : resolveDashboardHarnessSubmitIntent(input.text, {
16869
+ stickyBackend: dashboardAcpChat.getSticky(),
16870
+ source: input.externalSubmitSource,
16871
+ });
17467
16872
  const cmdText = submitIntent.kind === 'control-turn'
17468
16873
  ? (submitIntent.commandText ?? '')
17469
16874
  : submitIntent.text;
17470
- if (submitIntent.kind === 'submit-turn' && parseSeatAsk(submitIntent.text)) {
16875
+ if (submitIntent.kind === 'submit-turn' && !internalTurn && parseSeatAsk(submitIntent.text)) {
17471
16876
  try {
17472
16877
  const receipt = await askSeat(submitIntent.text, { channel: 'tui', clientId: tuiSeatAskClientId },
17473
16878
  ceoTaskDeps(getUserConfig(), null), tuiSeatAskDeps);
@@ -17662,7 +17067,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17662
17067
  openDetailViewer: (title, lines) => { setDetailViewer(title, lines); },
17663
17068
  },
17664
17069
  undo: {
17665
- refreshGitDirty: () => { refreshGitDirty(getSessionCwd(), { force: true }); },
17070
+ refreshGitDirty: () => { void refreshGitDirtyAsync(getSessionCwd(), { force: true }).then(() => { try { draw(); } catch { /* noop */ } }, () => { /* noop */ }); },
17666
17071
  bold: C.bold,
17667
17072
  },
17668
17073
  delta: {
@@ -18048,6 +17453,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18048
17453
  pushTurnSeparator: () => { pushTurnSeparator(); },
18049
17454
  getLogFoldMode: () => logFoldMode,
18050
17455
  setLogFoldMode: (mode) => { logFoldMode = mode; },
17456
+ setAllFolds: (expanded) => foldStack.setAll(expanded),
18051
17457
  copyEntireLog: async () => { await copyLogBlock(-1, 'all'); },
18052
17458
  returnFocusToInput: () => {
18053
17459
  const sourceFocus = workingDir.focus;
@@ -18136,6 +17542,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18136
17542
  ? { url: opts.remote.url, token: opts.remote.token }
18137
17543
  : null,
18138
17544
  acpSwapTo: (sessionId, cwd) => dashboardAcpSession.swapTo(sessionId, cwd),
17545
+ acpRenew: async (cwd) => {
17546
+ const renewed = await dashboardAcpSession.renew(cwd);
17547
+ acpSessionIdRef.current = renewed;
17548
+ return renewed;
17549
+ },
18139
17550
  },
18140
17551
  shellSlash: {
18141
17552
  slashRuntime: shellSlashRuntime,
@@ -18145,6 +17556,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18145
17556
  },
18146
17557
  termSlash: {
18147
17558
  slashRuntime: termSlashRuntime,
17559
+ openNewTerminal: spawnGlobalTerminalModal,
18148
17560
  sessionRegistry: sessionRegistry as unknown as DashboardSlashContext['termSlash']['sessionRegistry'],
18149
17561
  terminalMatrix: terminalMatrix as unknown as DashboardSlashContext['termSlash']['terminalMatrix'],
18150
17562
  vwRegistry: {
@@ -18197,6 +17609,21 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18197
17609
  setWorkingFocusPreview: () => { setWorkingFocus('preview', 'slash-term-move-preview'); },
18198
17610
  nextTerminalVwSlotId: (terminalId) => `term-slot:${terminalId}`,
18199
17611
  },
17612
+ // TUI-LOOP-COMMAND — /loop <간격> <프롬프트> · 쉬는 동안만 발사
17613
+ // (src/dashboard/loop-scheduler.ts · 판정: idle 발사 1 · busy 미룸 · stop 0).
17614
+ // idle/busy 신호는 대시보드 루프가 이미 쓰는 것을 그대로 쓴다 —
17615
+ // 스트리밍 턴(streamingInFlight · reportTuiTurn busy/idle) +
17616
+ // ACP 세션 바쁨(dashboardAcpChat.isBusy()). 프롬프트 제출은
17617
+ // 플러그인 submitDashboardText 와 같은 기존 제출 틈
17618
+ // (dispatchDashboardSubmitText)으로 낸다 — 새 제출 경로를 만들지 않는다.
17619
+ loopCommand: {
17620
+ isIdle: () => !streamingInFlight && !dashboardAcpChat.isBusy(),
17621
+ submitPrompt: (prompt: string) => {
17622
+ if (dispatchDashboardSubmitText) dispatchDashboardSubmitText(prompt);
17623
+ else chatLines.push(C.muted(' /loop: 입력 루프가 아직 제출을 받지 않는다 — 다음 틱을 기다린다'));
17624
+ },
17625
+ schedulerRef: dashboardLoopSchedulerRef,
17626
+ },
18200
17627
  };
18201
17628
  const slashOutcome = await dashboardSlashRegistry.dispatch(cmdLower, args, slashCtx);
18202
17629
  if (debug.enabled) {
@@ -18644,7 +18071,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18644
18071
  // pattern. Without this draw(), the echo sits in the chatLines
18645
18072
  // array but doesn't paint until after the skill router's
18646
18073
  // classifier LLM call returns (~1-2s of visual silence).
18647
- echoSubmittedUserText(tok.text);
18074
+ if (internalTurn) {
18075
+ chatLines.push(C.muted(` ${formatInternalTurnBanner(internalTurn)}`));
18076
+ chatScrollOffset = -1;
18077
+ } else {
18078
+ echoSubmittedUserText(tok.text);
18079
+ }
18648
18080
  if (queuedTurnEndText !== undefined && input.text === queuedTurnEndText) {
18649
18081
  debug.log('dashboard.turn-typeahead', 'echoed', { path: 'turn-end', length: queuedTurnEndText.length });
18650
18082
  }
@@ -18862,7 +18294,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18862
18294
  let latestTurnUsage: import('../prompt-cache/types.js').LLMUsage | undefined;
18863
18295
  const runPlainSubmitTurn = (submit: PlainTurnSubmit) => runDashboardChatMainPlainTurn({
18864
18296
  // ⛔⭐ 관측 심을 «실제로» 넘긴다 — 타입만 선택적으로 넓히면 조용히 안 온다(`F12`).
18865
- debugLog: (category, event, payload) => { try { debug.log(category, event, payload); } catch { /* fail-soft */ } },
18297
+ debugLog: (category, event, payload, opts) => { try { debug.log(category, event, payload, opts); } catch { /* fail-soft */ } },
18866
18298
  userText: tok.text,
18867
18299
  intent: submit.intent,
18868
18300
  contextText: ctx,
@@ -18950,10 +18382,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18950
18382
  userConfig: args.userConfig,
18951
18383
  rich: false,
18952
18384
  enabledTools: tools.map(t => t.name),
18385
+ seatBoot: true,
18953
18386
  }),
18954
18387
  ];
18955
18388
  },
18956
- runAutoCompact: async ({ preamble, chatHistory, userMsg }) => {
18389
+ runAutoCompact: async ({ preamble, chatHistory, userMsg, onPrePassApplied }) => {
18957
18390
  const compactMod = await import('../compact/index.js');
18958
18391
  const { shouldAutoCompact, compactConversationPartial, compactConversation, runCompactPipeline, setVerifyProbeEnabled } = compactMod;
18959
18392
  const { renderCompactBoundary } = await import('../chat/compact-boundary.js');
@@ -18992,8 +18425,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18992
18425
  getCompactProvider: () => getDefaultCompactProvider(),
18993
18426
  setVerifyProbeEnabled,
18994
18427
  sessionId: attachedSessionId ?? undefined,
18428
+ onPrePassApplied,
18995
18429
  });
18996
18430
  },
18431
+ readToolDispatchCount: () => opts.remote ? null : acpToolDispatchCount,
18997
18432
  attachChatStreamingKeys,
18998
18433
  createOptionalSpecs: async () => {
18999
18434
  const uc = (await import('../user-config.js')).getUserConfig();
@@ -19117,7 +18552,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19117
18552
  recordTurnMetrics: async (fullResponse) => {
19118
18553
  await recordDashboardTurnMetrics({
19119
18554
  chatHistory: chat.history as ChatMessage[],
19120
- model: inspectActiveProvider().model,
18555
+ // 실제 턴 모델로 기록한다 — 설정 모델이면 ctx 최대치·단가가 다른 모델 것이 된다.
18556
+ // ACP 서버가 사용량에 실어 보낸 턴 모델 → 없으면 백엔드가 확인한 runtime 모델 → 설정.
18557
+ model: latestTurnUsage?.model ?? inspectActiveProvider(undefined, activeAcpRuntimeModel()).model,
19121
18558
  fullResponse,
19122
18559
  turnStartedAt,
19123
18560
  usage: latestTurnUsage,
@@ -19180,6 +18617,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19180
18617
  },
19181
18618
  warningLine: (message) => C.warning(`${ICONS.warning} ${message}`),
19182
18619
  pushErrorLine: (message) => { chatLines.push(C.error(`Error: ${message}`)); },
18620
+ pushFailureNotice: (notice, raw) => {
18621
+ chatLines.push(C.warning(notice));
18622
+ detailViewerRuntime.stage('turn error', raw.split(/\r?\n/));
18623
+ },
19183
18624
  finalizeTurn: (finalStatus, finalError) => {
19184
18625
  lastTurnFinalStatus = finalStatus;
19185
18626
  clearEscAbortToolState(escAbortToolState);
@@ -19213,7 +18654,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19213
18654
  const { resumeGoalFromUserFollowUp } = await import('../goals/chat-loop-bridge.js');
19214
18655
  const { hydrateGoalFromSnapshot } = await import('../goals/registry.js');
19215
18656
  hydrateGoalFromSnapshot();
19216
- const followUpAction = tok.text.startsWith('/goal')
18657
+ // ⛔ H2 — 내부 턴은 사람의 «정정·재개» 발화가 아니다: 멈춘 /goal 을 되살리지 않는다.
18658
+ const followUpAction = tok.text.startsWith('/goal') || internalTurn
19217
18659
  ? { kind: 'no-loop' as const }
19218
18660
  : resumeGoalFromUserFollowUp(tok.text);
19219
18661
  let goalLoopUserText: string | null = followUpAction.kind === 'continue'
@@ -19224,7 +18666,15 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19224
18666
  }
19225
18667
  let goalLoopBudget = 100; // hard safety cap independent of /goal budget
19226
18668
  let lastTurnAborted = false;
18669
+ let goalLoopIteration = 0;
19227
18670
  do {
18671
+ // 앞 반복의 finalizeTurn 이 손잡이를 멈췄다 — 반복이 «실제로 시작될 때만» 새 표시를 단다(#5).
18672
+ thinking = goalLoopIterationThinking(goalLoopIteration++, thinking, () => startPinnedThinking({
18673
+ target: chatFooterLine,
18674
+ onFrame: () => { pinChatTail(); draw(); },
18675
+ message: 'Thinking',
18676
+ metrics: { startedAt: Date.now(), hint: 'esc 중단' },
18677
+ }));
19228
18678
  const turnText = goalLoopUserText ?? tok.text;
19229
18679
  await runDashboardChatMainSubmitIntent({
19230
18680
  submit: createDashboardChatMainTurnSubmit({