elanous 0.2.18 → 0.2.19

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 (404) 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/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
  28. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
  110. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  111. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
  112. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  113. package/apps/pwa/out/approvals/index.html +1 -1
  114. package/apps/pwa/out/approvals/index.txt +23 -23
  115. package/apps/pwa/out/autopilot/index.html +1 -1
  116. package/apps/pwa/out/autopilot/index.txt +23 -23
  117. package/apps/pwa/out/board/index.html +1 -1
  118. package/apps/pwa/out/board/index.txt +23 -23
  119. package/apps/pwa/out/botlab/index.html +1 -1
  120. package/apps/pwa/out/botlab/index.txt +22 -22
  121. package/apps/pwa/out/bots/index.html +1 -1
  122. package/apps/pwa/out/bots/index.txt +22 -22
  123. package/apps/pwa/out/ceo/index.html +1 -1
  124. package/apps/pwa/out/ceo/index.txt +23 -23
  125. package/apps/pwa/out/chat/index.html +1 -1
  126. package/apps/pwa/out/chat/index.txt +22 -22
  127. package/apps/pwa/out/consult/index.html +1 -1
  128. package/apps/pwa/out/consult/index.txt +23 -23
  129. package/apps/pwa/out/control/index.html +1 -1
  130. package/apps/pwa/out/control/index.txt +23 -23
  131. package/apps/pwa/out/decisions/index.html +1 -1
  132. package/apps/pwa/out/decisions/index.txt +22 -22
  133. package/apps/pwa/out/design-check/index.html +1 -1
  134. package/apps/pwa/out/design-check/index.txt +23 -23
  135. package/apps/pwa/out/editor/index.html +1 -1
  136. package/apps/pwa/out/editor/index.txt +25 -24
  137. package/apps/pwa/out/exec/index.html +1 -1
  138. package/apps/pwa/out/exec/index.txt +23 -23
  139. package/apps/pwa/out/field/index.html +1 -1
  140. package/apps/pwa/out/field/index.txt +23 -23
  141. package/apps/pwa/out/healthz +1 -1
  142. package/apps/pwa/out/index.html +1 -1
  143. package/apps/pwa/out/index.txt +22 -22
  144. package/apps/pwa/out/inside/index.html +1 -1
  145. package/apps/pwa/out/inside/index.txt +24 -23
  146. package/apps/pwa/out/intake/index.html +1 -1
  147. package/apps/pwa/out/intake/index.txt +23 -23
  148. package/apps/pwa/out/live/index.html +1 -1
  149. package/apps/pwa/out/live/index.txt +23 -23
  150. package/apps/pwa/out/loops/index.html +1 -1
  151. package/apps/pwa/out/loops/index.txt +28 -27
  152. package/apps/pwa/out/market/index.html +1 -1
  153. package/apps/pwa/out/market/index.txt +22 -22
  154. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  155. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  156. package/apps/pwa/out/morning/index.html +1 -1
  157. package/apps/pwa/out/morning/index.txt +23 -23
  158. package/apps/pwa/out/observatory/index.html +1 -1
  159. package/apps/pwa/out/observatory/index.txt +23 -23
  160. package/apps/pwa/out/ops/checklist/index.html +1 -1
  161. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  162. package/apps/pwa/out/ops/release/index.html +1 -1
  163. package/apps/pwa/out/ops/release/index.txt +22 -22
  164. package/apps/pwa/out/ops/seats/index.html +1 -1
  165. package/apps/pwa/out/ops/seats/index.txt +22 -22
  166. package/apps/pwa/out/outputs/index.html +1 -1
  167. package/apps/pwa/out/outputs/index.txt +22 -22
  168. package/apps/pwa/out/reflection/index.html +1 -1
  169. package/apps/pwa/out/reflection/index.txt +23 -23
  170. package/apps/pwa/out/scheduler/index.html +1 -1
  171. package/apps/pwa/out/scheduler/index.txt +23 -23
  172. package/apps/pwa/out/sessions/index.html +1 -1
  173. package/apps/pwa/out/sessions/index.txt +22 -22
  174. package/apps/pwa/out/settings/devices/index.html +1 -1
  175. package/apps/pwa/out/settings/devices/index.txt +23 -23
  176. package/apps/pwa/out/settings/index.html +1 -1
  177. package/apps/pwa/out/settings/index.txt +23 -23
  178. package/apps/pwa/out/setup/done/index.html +1 -1
  179. package/apps/pwa/out/setup/done/index.txt +23 -23
  180. package/apps/pwa/out/setup/index.html +1 -1
  181. package/apps/pwa/out/setup/index.txt +23 -23
  182. package/apps/pwa/out/share/index.html +1 -1
  183. package/apps/pwa/out/share/index.txt +23 -23
  184. package/apps/pwa/out/showroom/index.html +1 -1
  185. package/apps/pwa/out/showroom/index.txt +22 -22
  186. package/apps/pwa/out/tasks/index.html +1 -1
  187. package/apps/pwa/out/tasks/index.txt +23 -23
  188. package/apps/pwa/out/term/index.html +1 -1
  189. package/apps/pwa/out/term/index.txt +23 -23
  190. package/apps/pwa/out/today/index.html +1 -1
  191. package/apps/pwa/out/today/index.txt +23 -23
  192. package/apps/pwa/out/trace/index.html +1 -1
  193. package/apps/pwa/out/trace/index.txt +23 -23
  194. package/apps/pwa/out/vault/index.html +1 -1
  195. package/apps/pwa/out/vault/index.txt +23 -23
  196. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  197. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  198. package/apps/pwa/out/workflows/index.html +1 -1
  199. package/apps/pwa/out/workflows/index.txt +23 -23
  200. package/apps/pwa/out/workspace/index.html +1 -1
  201. package/apps/pwa/out/workspace/index.txt +23 -23
  202. package/apps/pwa/out/worktrees/index.html +1 -1
  203. package/apps/pwa/out/worktrees/index.txt +23 -23
  204. package/graphs/ops/guardian.yaml +26 -0
  205. package/graphs/ops/recipes.yaml +3 -0
  206. package/package.json +1 -1
  207. package/scripts/coord-channel-watch.sh +2 -2
  208. package/scripts/discord-seat-probe.ts +3 -1
  209. package/scripts/lib/pch-regress-map.ts +23 -0
  210. package/scripts/morning-report.ts +18 -4
  211. package/scripts/pch-regress.ts +88 -0
  212. package/scripts/release-loop/gate-diff.ts +18 -0
  213. package/scripts/release-loop/gate-node.ts +338 -46
  214. package/scripts/release-loop/gate-partial.ts +106 -0
  215. package/scripts/release-loop/gate-timing.ts +120 -0
  216. package/scripts/release-loop/resume-release.ts +64 -5
  217. package/scripts/release-loop/shard-plan.ts +65 -0
  218. package/scripts/release-loop/unattended-release.ts +2 -0
  219. package/scripts/video/scene-table.ts +159 -0
  220. package/src/acp/core-turn-bridge.ts +39 -2
  221. package/src/away/presence.ts +40 -0
  222. package/src/away/release-watch.ts +255 -0
  223. package/src/chat/index.ts +2 -1
  224. package/src/chat/short-question.ts +3 -40
  225. package/src/cli/away-cli.ts +68 -0
  226. package/src/cli/guardian-cli.ts +49 -0
  227. package/src/cli/intake-cli.ts +13 -7
  228. package/src/cli/log-event-names.ts +1 -0
  229. package/src/cli/pod-cli.ts +5 -1
  230. package/src/cli/pty-drive-cli.ts +14 -3
  231. package/src/cli/release-cli.ts +32 -7
  232. package/src/cli/setup-cli.ts +31 -1
  233. package/src/cli/tasks-cli.ts +104 -11
  234. package/src/context-bus/context-now-public.ts +1 -1
  235. package/src/context-bus/context-now-surfaces.ts +16 -6
  236. package/src/context-bus/context-now.ts +97 -3
  237. package/src/core-turn/short-question.ts +100 -0
  238. package/src/dashboard/dashboard-status-lines.ts +24 -4
  239. package/src/dashboard/index.ts +50 -5
  240. package/src/dashboard/input/slash-executor.ts +6 -2
  241. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  242. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  243. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  244. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  245. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  246. package/src/domains/guardian-tick.ts +138 -0
  247. package/src/domains/outbound-alert.ts +44 -13
  248. package/src/domains/schedule-registry.ts +23 -0
  249. package/src/domains/telegram-kind-route.ts +39 -0
  250. package/src/domains/watch-default-scheduler.ts +95 -0
  251. package/src/esc-abort-gate.ts +44 -0
  252. package/src/graph-runner/runner.ts +25 -5
  253. package/src/harness/harness-cli-command.ts +2 -0
  254. package/src/harness/harness-salvage-cli.ts +78 -0
  255. package/src/harness/harness-worktree-add.ts +68 -0
  256. package/src/harness/ledger-write-guard.ts +6 -16
  257. package/src/hq/hq.ts +6 -1
  258. package/src/index.ts +52 -3
  259. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  260. package/src/instance/test-write-guard.ts +200 -0
  261. package/src/intake-plane/digest.ts +39 -15
  262. package/src/intake-plane/harness-target.ts +4 -3
  263. package/src/intake-plane/intake-sources.ts +1 -0
  264. package/src/intake-plane/items.ts +1 -0
  265. package/src/loops/orchestrator/degradation.ts +57 -0
  266. package/src/loops/orchestrator/finish-rate.ts +33 -3
  267. package/src/loops/orchestrator/tick.ts +166 -45
  268. package/src/maturity/feature-maturity.ts +6 -4
  269. package/src/maturity/tui-slash-maturity.ts +1 -0
  270. package/src/mss/logging/log-store.ts +3 -0
  271. package/src/nexus/api/http-server.ts +16 -5
  272. package/src/nexus/api/loop-edges.ts +290 -17
  273. package/src/nexus/api/ops-api.ts +96 -28
  274. package/src/nexus/index.ts +7 -0
  275. package/src/release-loop/checklist.ts +2 -0
  276. package/src/release-loop/feature-store.ts +54 -7
  277. package/src/release-loop/light-rc.ts +173 -0
  278. package/src/release-loop/placement.ts +51 -17
  279. package/src/release-loop/rubric.ts +57 -1
  280. package/src/self-dev/dev-cli.ts +36 -0
  281. package/src/self-dev/orchestrate.ts +1 -0
  282. package/src/self-dev/pr-preview.ts +179 -0
  283. package/src/self-dev/shard-run.ts +23 -7
  284. package/src/self-implement/gate-cli.ts +29 -8
  285. package/src/self-implement/goal-author-clarification.ts +88 -13
  286. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  287. package/src/self-implement/orchestrator.ts +2 -1
  288. package/src/self-implement/salvage-branches.ts +254 -0
  289. package/src/task-agent/actions.ts +63 -2
  290. package/src/task-agent/board.ts +132 -0
  291. package/src/task-agent/card-evidence.ts +203 -0
  292. package/src/task-agent/completion-evidence.ts +126 -0
  293. package/src/task-agent/judge.ts +18 -0
  294. package/src/task-agent/live-moves.ts +176 -0
  295. package/src/task-agent/mission.ts +32 -6
  296. package/src/task-agent/shadow.ts +183 -15
  297. package/src/task-agent/task-hand.ts +117 -15
  298. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  299. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  300. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  301. package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
  302. package/src/telegram-away-command.ts +38 -0
  303. package/src/telegram-commands.ts +11 -0
  304. package/src/telegram-report-log-probe.ts +21 -0
  305. package/src/telegram-report.ts +27 -3
  306. package/src/tui-client/dashboard-session.ts +1 -0
  307. package/src/user-config.ts +134 -2
  308. package/src/version/packed-revision.json +1 -1
  309. package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  314. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +0 -2
  335. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  344. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/design-check/page-5d97b2901e0d1ea6.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/exec/page-8c3d8e63ae2e1c98.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1467047104141297.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/workspace/page-134da468538baf48.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
  403. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  404. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -0,0 +1,100 @@
1
+ // B1(UX · 0.2.7) — 짧은 «물음»은 도구 없이 빠른 경로로(첫 글자 ≤2초 · 내부 문서 `EVAL-bot-chat-vs-grok-2026-09-30`).
2
+ // 허용 방식(골 ASK-bot-short-question-fast-path-b1b): 아래가 «모두» 참일 때만 fast — 하나라도 거짓이면 fast 아님(보수 쪽).
3
+ // ⓐ 한 줄 · 80자 이하 · 첨부 없음 ⓑ 인사이거나 «물음 꼴» ⓒ 요청·작업 표지 · 경로·URL·코드 꼴 · 실시간 낱말이 없다.
4
+ // 거부 목록으로 변형을 다 막을 수는 없다 — 오분류 비용은 «확인해 볼까요?» 한 줄로 막힌다(도구가 안 실릴 뿐 대화는 이어진다).
5
+ // 판정 표본 = src/chat/short-question.test.ts 의 고정 표 20개(⊕ 리뷰가 든 변형).
6
+
7
+ export interface ShortQuestionContext {
8
+ hasAttachments?: boolean;
9
+ }
10
+ export interface ShortQuestionVerdict {
11
+ fast: boolean;
12
+ reason: string;
13
+ }
14
+
15
+ const GREETING = /^(?:안녕(?:하세요|하십니까)?|반가워(?:요)?|고마워(?:요)?|감사합니다|하이|hi|hello|hey|thanks|thank you)[!?.~\s]*$/i;
16
+ // «한 줄로» 같은 짧은 꼬리를 떼고 본다.
17
+ const TAIL = /\s*(?:한\s*줄로|짧게|간단히|briefly|in one line)[.!]?\s*$/i;
18
+ const KO_QUESTION_END = /(?:\?|까|니|나요|냐|야|어때|뭐|몇|언제|어디|누구|왜|인가|일까)[?.!~]*$/;
19
+ const EN_QUESTION = /^(?:what|why|how|who|where|when|which|is|are|does|do)\b.*\?$/i;
20
+ const REQUEST_KO = /(?:해\s*줘|해\s*주세요|줘(?:요)?(?=[?.!\s]|$)|주세요|줄래|할래|볼래|줄\s*수\s*있|돌려|실행|고쳐|만들어\s*줘|찾아|검색|확인해|열어|보내|올려|지워|써\s*줘|작성해|요약해|정리해|분석해)/;
21
+ const REQUEST_EN = /\b(?:please|can you|could you|would you|run|test|tests|fix|build|open|search|check|send|create|write|deploy|install|summarize)\b/i;
22
+ const TASK = /(?:작업|파일|폴더|디렉터리|레포|저장소|커밋|브랜치|\bPR\b|코드|스크립트|테스트|빌드|배포|설치|설정|명령어|로그|버그|이슈)/i;
23
+ const PATH_OR_CODE = /(?:https?:\/\/|www\.|[\w.-]+\/[\w./-]+|\.(?:ts|tsx|js|py|md|json|yaml|swift|kt)\b|[`{};=<>])/i;
24
+ const LIVE = /(?:오늘\s*(?:날짜|며칠|무슨\s*요일)|지금\s*몇\s*시|최신|뉴스|속보|주가|시세|환율|날씨|today'?s|latest|news|stock|weather|right now)/i;
25
+
26
+ export type TuiChatRoute = '조회' | '설정 변경' | '조사' | '상시 일' | '코딩·구현';
27
+
28
+ export interface TuiChatVerdict {
29
+ route: TuiChatRoute;
30
+ reason: string;
31
+ goalLoop: boolean;
32
+ }
33
+
34
+ /** Request verbs a quick lookup/settings/research/scheduling turn can carry out without the repository. */
35
+ const QUICK_REQUEST_VERBS = /^(?:보여|알려|확인해|찾아|찾아봐|조사해|검색해|정리해|요약해|바꿔|변경해|전환해|설정해|맞춰|수정해|고쳐|만들어|등록해|예약해|추가해|보내)$/;
36
+
37
+ /** True when the verb never appears, or each occurrence directly follows an allowed object. */
38
+ function everyVerbTakes(text: string, verb: RegExp, object: RegExp): boolean {
39
+ for (const match of text.matchAll(verb)) {
40
+ if (!object.test(text.slice(0, match.index))) return false;
41
+ }
42
+ return true;
43
+ }
44
+
45
+ /** Uncertain or mixed requests retain the full coding goal-loop and tool surface. */
46
+ export function classifyTuiChatIntent(text: string, ctx: ShortQuestionContext = {}): TuiChatVerdict {
47
+ const q = text.trim();
48
+ const coding = (reason: string): TuiChatVerdict => ({ route: '코딩·구현', reason, goalLoop: true });
49
+ if (!q || ctx.hasAttachments) return coding(ctx.hasAttachments ? 'attachment' : 'empty');
50
+ if (/(?:하니스|구현|코딩|코드|스크립트|버그|소스|리팩터|커밋|패치|테스트|시험\s*돌|빌드|배포|브랜치|서버|파일|레포|저장소|\bPR\b|\b(?:implement|coding|code|script|refactor|commit|patch|tests?|build|deploy|branch|server|file|repo)\b)/i.test(q)) return coding('coding');
51
+ // Destructive or executing verbs in any form (not only «…줘») are never a quick turn.
52
+ if (/(?:지워|지우|삭제|제거|옮겨|옮기|돌려|실행|설치|되돌려|롤백|재시작|죽여|\b(?:delete|remove|rm|rename|move|run|install|revert|rollback|drop|kill|restart)\b)/i.test(q)) return coding('write-verb');
53
+ // A file path or code shape means the request touches the repository, whatever else it asks.
54
+ if (PATH_OR_CODE.test(q)) return coding('path-or-code');
55
+ // Allowlist, not denylist: every «…줘» request verb must be one a quick turn can do; anything else
56
+ // (지워줘 · 돌려줘 · 옮겨줘 …) keeps the coding goal loop.
57
+ for (const match of q.matchAll(/([가-힣]+?)\s*(?:줘|주세요|줄래|주라)/g)) {
58
+ if (!QUICK_REQUEST_VERBS.test(match[1]!)) return coding('request-verb');
59
+ }
60
+ // Edit and create verbs are coding requests unless every one of them takes a quick object directly
61
+ // («모델 설정 수정해줘» · «알림 만들어줘» stay quick; «… 그리고 화면 고쳐줘» does not).
62
+ if (!everyVerbTakes(q, /(?:수정|고쳐|바꿔|변경|전환|교체|\b(?:edit|fix|modify|change|switch|replace)\b)/gi, /(?:모델|설정|config|provider|예약|일정|알림|리마인더|스케줄|reminder|schedule)\s*(?:을|를|로|으로)?\s*$/i)) return coding('edit');
63
+ if (!everyVerbTakes(q, /(?:만들어|작성|추가해|생성|등록해|\b(?:create|write|make|add)\b)/gi, /(?:알림|일정|예약|리마인더|스케줄|reminder|schedule)\s*(?:을|를)?\s*$/i)) return coding('create');
64
+
65
+ const routes: Array<{ route: Exclude<TuiChatRoute, '코딩·구현'>; reason: string; pattern: RegExp }> = [
66
+ { route: '조회', reason: 'lookup', pattern: /(?:결정\s*카드|결정\s*대기|상태|현황|로그|알림\s*(?:왔|있|보여)|\b(?:status|logs?|pending decisions?)\b)/i },
67
+ { route: '설정 변경', reason: 'settings', pattern: /(?:(?:모델|설정|config|provider).*(?:바꿔|변경|전환|설정해|맞춰|수정|고쳐|써\s*줘|set|change|switch|edit|fix|modify)|(?:바꿔|변경|전환).*(?:모델|설정|config|provider)|답변.*빠른\s*모델)/i },
68
+ { route: '조사', reason: 'research', pattern: /(?:조사|검색|찾아\s*봐|뉴스|업계\s*소식|\b(?:research|search|news)\b)/i },
69
+ { route: '상시 일', reason: 'recurring', pattern: /(?:매일|매주|매달|매\s*아침|[가-힣]+마다|정기적|반복\s*일정|예약|리마인더|\b(?:daily|weekly|every|schedule|remind)\b)/i },
70
+ ];
71
+ const matches = routes.filter(({ pattern }) => pattern.test(q));
72
+ // A recurring job that looks something up or researches it is still a scheduling request.
73
+ if (matches.some(({ route }) => route === '상시 일')
74
+ && matches.every(({ route }) => route === '상시 일' || route === '조사' || route === '조회')) {
75
+ return { route: '상시 일', reason: matches.length > 1 ? 'recurring-research' : 'recurring', goalLoop: false };
76
+ }
77
+ if (matches.length !== 1) {
78
+ if (matches.length) return coding('mixed');
79
+ const shortQuestion = classifyShortQuestion(q, ctx);
80
+ return coding(shortQuestion.fast ? `short-${shortQuestion.reason}` : 'ambiguous');
81
+ }
82
+ const { route, reason } = matches[0]!;
83
+ return { route, reason, goalLoop: false };
84
+ }
85
+
86
+ export function classifyShortQuestion(text: string, ctx: ShortQuestionContext = {}): ShortQuestionVerdict {
87
+ const q = text.trim();
88
+ if (!q) return { fast: false, reason: 'empty' };
89
+ if (ctx.hasAttachments) return { fast: false, reason: 'attachment' };
90
+ if (/[\r\n\u2028\u2029]/.test(q)) return { fast: false, reason: 'multiline' };
91
+ if ([...q].length > 80) return { fast: false, reason: 'long' };
92
+ if (PATH_OR_CODE.test(q)) return { fast: false, reason: 'path-or-code' };
93
+ if (LIVE.test(q)) return { fast: false, reason: 'live' };
94
+ if (REQUEST_KO.test(q) || REQUEST_EN.test(q)) return { fast: false, reason: 'request' };
95
+ if (TASK.test(q)) return { fast: false, reason: 'task' };
96
+ if (GREETING.test(q)) return { fast: true, reason: 'greeting' };
97
+ const core = q.replace(TAIL, '').trim();
98
+ if (KO_QUESTION_END.test(core) || EN_QUESTION.test(core)) return { fast: true, reason: 'question' };
99
+ return { fast: false, reason: 'not-a-question' };
100
+ }
@@ -16,25 +16,45 @@ export interface DashboardStatusLinesInput {
16
16
  readonly acp: string;
17
17
  }
18
18
 
19
+ /** Detail block marker — everything from the blank line before it on is shown only with `/status --debug`. */
20
+ export const DASHBOARD_STATUS_DETAIL_HEADING = '자세히';
21
+
22
+ // F5 (TUI-COMFORT) — «연결: 데몬 모름 안 붙음» read as broken. Say what is true in plain words.
23
+ function connectionLine(address: string | null, connected: boolean): string {
24
+ if (address) return `연결: 데몬 ${address} ${connected ? '붙음' : '안 붙음'}`;
25
+ return connected ? '연결: 데몬 붙음' : '연결: 혼자 돎(데몬 없음)';
26
+ }
27
+
28
+ /**
29
+ * `/status` default view: the three human lines (⊕ the seats line) only. Internal fields
30
+ * (`surface`·`acp`·`chatOnly`…) stay behind `/status --debug`. Lines without a detail block pass through.
31
+ */
32
+ export function dashboardStatusSummaryLines(lines: readonly string[]): string[] {
33
+ const detail = lines.indexOf(DASHBOARD_STATUS_DETAIL_HEADING);
34
+ if (detail < 0) return [...lines];
35
+ const end = detail > 0 && lines[detail - 1] === '' ? detail - 1 : detail;
36
+ return [...lines.slice(0, end), ' 자세히: /status --debug'];
37
+ }
38
+
19
39
  export function buildDashboardStatusLines(input: DashboardStatusLinesInput): string[] {
20
40
  const known = (value?: string | null): string => value?.trim() || '모름';
21
41
  // A remote URL can contain credentials or query parameters. Neither belongs in /status.
22
- let address = '모름';
42
+ let address: string | null = null;
23
43
  if (input.daemonAddress?.trim()) {
24
44
  try {
25
45
  const url = new URL(input.daemonAddress);
26
46
  address = `${url.protocol}//${url.host}`;
27
47
  } catch {
28
- address = '모름';
48
+ address = null;
29
49
  }
30
50
  }
31
51
  return [
32
52
  `모델: ${known(input.provider)}/${known(input.model)} (${known(input.reasoning)})`,
33
- `연결: 데몬 ${address} ${input.daemonConnected ? '붙음' : '안 붙음'}`,
53
+ connectionLine(address, input.daemonConnected),
34
54
  `계정: ${input.accountName?.trim() === 'default' ? '기본' : known(input.accountName)}`,
35
55
  ...(input.seatsNow ? [input.seatsNow] : []),
36
56
  '',
37
- '자세히',
57
+ DASHBOARD_STATUS_DETAIL_HEADING,
38
58
  ...input.sessionSurfaceLines.map(line => ` ${line}`),
39
59
  ` view: ${input.view}`,
40
60
  ` focus: ${input.focus}`,
@@ -1323,7 +1323,7 @@ import {
1323
1323
  AGENT_ACTIVITY_SEGMENT_KEY,
1324
1324
  PULSE_HALF_PERIOD_MS,
1325
1325
  } from '../agent-activity-hud.js';
1326
- import { createEscAbortGate, routeStreamingEscapeKey } from '../esc-abort-gate.js';
1326
+ import { DASHBOARD_CLARIFICATION_REQUEST, consumeStreamingTopModalEscape, createEscAbortGate, createHarnessEscHint, routeStreamingEscapeKey } from '../esc-abort-gate.js';
1327
1327
  import { createViEditor, type ViEditorHandle } from '../vi-editor.js';
1328
1328
  import { launchEditor, canLaunchEditor } from '../editor-launcher.js';
1329
1329
  import { createSshPickerModal } from '../ssh/ssh-picker-modal.js';
@@ -6700,6 +6700,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6700
6700
  * this function is invoked.
6701
6701
  */
6702
6702
  const escAbortToolState = createEscAbortToolState();
6703
+ let clarificationModalId: string | null = null;
6703
6704
 
6704
6705
  const attachChatStreamingKeys = (abortCtrl: AbortController): () => void => {
6705
6706
  const chatLinesStartIndex = chatLines.length;
@@ -6710,24 +6711,31 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6710
6711
  // work is running, pop a confirmation modal before tearing down
6711
6712
  // the stream — the running count is visible in the HUD (T3-B1)
6712
6713
  // so the user can make an informed call.
6714
+ let abortGateModalId: string | null = null;
6715
+ const showHarnessEscHint = createHarnessEscHint((line) => pushChatLine(line));
6713
6716
  const escGate = createEscAbortGate({
6714
6717
  abortCtrl,
6715
6718
  getRunningCount: () => getEscAbortRunningCount(escAbortToolState),
6716
6719
  mountModal: (surface) => {
6717
6720
  attachSurfaceToWorkspace(surface, currentWorkspaceOwnerId());
6721
+ abortGateModalId = surface.id;
6718
6722
  const h = display.pushModal(surface);
6719
- return () => { try { h.dispose(); } catch { /* ignore */ } };
6723
+ return () => { abortGateModalId = null; try { h.dispose(); } catch { /* ignore */ } };
6720
6724
  },
6721
6725
  getViewport: () => termSize(),
6722
6726
  requestRedraw: () => draw(),
6723
6727
  getTheme: () => currentThemeTokens(),
6724
6728
  getWaitingTargetNames: () => getEscAbortWaitingToolNames(escAbortToolState),
6725
6729
  onAbortPending: ({ targets }) => {
6726
- pushChatLine(` ⏳ 중단 요청됨 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6730
+ if (!showHarnessEscHint(targets)) {
6731
+ pushChatLine(` ⏳ 중단 요청됨 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6732
+ }
6727
6733
  chatScrollOffset = -1;
6728
6734
  },
6729
6735
  onAbortRepeat: ({ repeat, targets }) => {
6730
- pushChatLine(` ⏳ ESC 재시도 ${repeat}회 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6736
+ if (!showHarnessEscHint(targets)) {
6737
+ pushChatLine(` ⏳ ESC 재시도 ${repeat}회 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6738
+ }
6731
6739
  chatScrollOffset = -1;
6732
6740
  },
6733
6741
  // ⭐⭐⭐ 턴을 멈추기 «전»에 살아남을 자식을 백그라운드 라우팅으로 넘긴다(`R4a` · 2026-08-19).
@@ -7183,6 +7191,28 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
7183
7191
  };
7184
7192
  return (await inputCoreRouteInputEventAsync(ev, ctx)) === 'consumed';
7185
7193
  },
7194
+ () => {
7195
+ const modal = topModalSurface({ focusStack: display.modalStack(), surfaceAt: id => display.surface(id) });
7196
+ if (modal?.windowRole === 'companion') return false;
7197
+ const askHandle = approvalModalRouter.currentKind() === 'askUser' ? approvalModalRouter.current() : null;
7198
+ const topModalId = () => topModalSurface({ focusStack: display.modalStack(), surfaceAt: id => display.surface(id) })?.id ?? null;
7199
+ return consumeStreamingTopModalEscape({
7200
+ modal,
7201
+ abortGateModalId,
7202
+ event: toDashboardKeyEvent(key),
7203
+ question: askHandle ? {
7204
+ id: askHandle.surface.id,
7205
+ dispose: (cancelled) => askHandle.dispose(cancelled),
7206
+ isClarification: askHandle.surface.id === clarificationModalId,
7207
+ } : undefined,
7208
+ onClarificationClosed: () => {
7209
+ pushChatLine('되묻기 창을 닫았다 — 답 없이 진행한다');
7210
+ chatScrollOffset = -1;
7211
+ },
7212
+ topModalId,
7213
+ redraw: draw,
7214
+ });
7215
+ },
7186
7216
  );
7187
7217
  if (streamingEscapeConsumed) return;
7188
7218
  if (routeStreamingApprovalModalKey(key)) return;
@@ -17649,11 +17679,26 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17649
17679
  // it via setExitInputLoop, or exit showDashboard via
17650
17680
  // { return: DashboardAction }.
17651
17681
  const dashboardSlashSurfaceUx = resolveDashboardSlashSurfaceUx();
17682
+ const clarificationSurfaceUx = dashboardSlashSurfaceUx ? {
17683
+ question: (request: Parameters<typeof dashboardSlashSurfaceUx.question>[0]) => {
17684
+ const pending = dashboardSlashSurfaceUx.question(request);
17685
+ if ((request as typeof request & { [DASHBOARD_CLARIFICATION_REQUEST]?: boolean })[DASHBOARD_CLARIFICATION_REQUEST]) {
17686
+ const handle = approvalModalRouter.current();
17687
+ if (approvalModalRouter.currentKind() === 'askUser' && handle) {
17688
+ clarificationModalId = handle.surface.id;
17689
+ void handle.promise.finally(() => {
17690
+ if (clarificationModalId === handle.surface.id) clarificationModalId = null;
17691
+ });
17692
+ }
17693
+ }
17694
+ return pending;
17695
+ },
17696
+ } : undefined;
17652
17697
  const slashCtx: DashboardSlashContext = {
17653
17698
  ad: { run: (args) => dispatchDashboardAdSlash(adSlashRuntime, [...args], () => { draw(); }) },
17654
17699
  chatLines,
17655
17700
  getStatusLines: getDashboardStatusLines,
17656
- ...(dashboardSlashSurfaceUx ? { surfaceUx: dashboardSlashSurfaceUx } : {}),
17701
+ ...(clarificationSurfaceUx ? { surfaceUx: clarificationSurfaceUx } : {}),
17657
17702
  attachmentRowMap,
17658
17703
  pushDebugLine,
17659
17704
  pushChatLine: (line) => { chatLines.push(line); },
@@ -1,5 +1,6 @@
1
1
  import type { SlashExecuteRequest, SlashExecuteResult } from '../../skills/tools/dashboard-slash.js';
2
2
  import type { PaneFocus } from '../../workspace-types.js';
3
+ import { dashboardStatusSummaryLines } from '../dashboard-status-lines.js';
3
4
 
4
5
  export interface ImmediateDashboardSlashDeps {
5
6
  getStatusLines: () => string[];
@@ -17,12 +18,15 @@ export function executeImmediateDashboardSlash(
17
18
  req: SlashExecuteRequest,
18
19
  deps: ImmediateDashboardSlashDeps,
19
20
  ): SlashExecuteResult | null {
20
- if ((req.name === 'status' || req.name === 'st') && req.args.length === 0) {
21
+ // `/status` = human lines only · `/status --debug` = with the internal detail block (TUI-SLASH-DECIDE-NOW C).
22
+ if ((req.name === 'status' || req.name === 'st')
23
+ && (req.args.length === 0 || (req.args.length === 1 && req.args[0] === '--debug'))) {
24
+ const lines = deps.getStatusLines();
21
25
  return {
22
26
  ok: true,
23
27
  name: req.name,
24
28
  args: req.args,
25
- logLines: deps.getStatusLines(),
29
+ logLines: req.args.length === 0 ? dashboardStatusSummaryLines(lines) : lines,
26
30
  };
27
31
  }
28
32
  if ((req.name === 'surface' || req.name === 'surf') && req.args.length >= 1) {
@@ -283,6 +283,8 @@ import { runRebindCommand } from '../../input-core/index.js';
283
283
  import { resolveRepositoryDesignCheck } from '../../cli/repo-cli.js';
284
284
  import { renderDesignCheckLines, type DesignCheckTone } from '../../design/design-check-render.js';
285
285
  import { handleDesignPick } from './design-pick.js';
286
+ import { createDecideSlashHandler } from './decide-slash.js';
287
+ import type { DecisionLedger } from '../../decisions/decision-ledger.js';
286
288
  import { listDesignDirections, parseDeclaredDirection } from '../../design/design-directions.js';
287
289
  import {
288
290
  dashboardDeltaHelpLines,
@@ -303,8 +305,6 @@ import {
303
305
  rotateNextProvider, jumpToRotationEntry, rotationEntryLabel,
304
306
  currentRotationIndex,
305
307
  } from '../../user-config.js';
306
- import type { LLMProviderName } from '../../user-config.js';
307
- import { lookupLlmTierSpec } from '../../model-tier/index.js';
308
308
  import { listProviders } from '../../llm.js';
309
309
  import { enabledModels, loadCatalog } from '../../intelligence-map/model-catalog.js';
310
310
  import { selfOrchestrateRuntime } from '../../self-dev/self-orchestrate-runtime.js';
@@ -343,6 +343,7 @@ import { terminalModalRouter } from '../input/terminal-modal-router.js';
343
343
  import { termSize, stripAnsi } from '../../tui.js';
344
344
  import { SLASH_COMMANDS } from '../../chat/index.js';
345
345
  import { buildEssentialHelpLines } from './help-from-registry.js';
346
+ import { HUMAN_MODEL_WORDS, MODEL_CODE_NAMES, modelChangeLine, modelOverviewLines, resolveModelChoice } from './model-choices.js';
346
347
  import type { FoldMode } from '../../log-entry.js';
347
348
  import { resolveDashboardChatMainLogCommand } from '../input/chat-main-log-command.js';
348
349
  import {
@@ -375,6 +376,7 @@ import { dirname as pathDirname, relative } from 'node:path';
375
376
  import { readFileSync, writeFileSync } from 'node:fs';
376
377
  import type { SurfaceUx } from '../../agent/surface-ux/types.js';
377
378
  import type { AskUserQuestionRequest } from '../../ask-user-question/types.js';
379
+ import { DASHBOARD_CLARIFICATION_REQUEST } from '../../esc-abort-gate.js';
378
380
  import type { GoalDocumentClarification } from '../../self-implement/goal-author-clarification.js';
379
381
  import { renderReplayPreviewLines } from '../replay-preview.js';
380
382
  import {
@@ -1295,6 +1297,7 @@ let selfOrchestrateSlashRuntime: SelfOrchestrateSlashRuntime = selfOrchestrateRu
1295
1297
  export async function readDashboardAskClarification(
1296
1298
  clarification: GoalDocumentClarification,
1297
1299
  surfaceUx: Pick<SurfaceUx, 'question'> | undefined,
1300
+ notify?: (line: string) => void,
1298
1301
  ): Promise<string> {
1299
1302
  if (!surfaceUx) return '';
1300
1303
  const request: AskUserQuestionRequest = {
@@ -1309,10 +1312,13 @@ export async function readDashboardAskClarification(
1309
1312
  includeOther: clarification.includeOther,
1310
1313
  }],
1311
1314
  };
1315
+ Object.defineProperty(request, DASHBOARD_CLARIFICATION_REQUEST, { value: true });
1312
1316
  try {
1313
- const result = await surfaceUx.question(request);
1314
- if (!result || result.cancelled) return '';
1317
+ const pending = surfaceUx.question(request);
1315
1318
  const id = clarification.questionId;
1319
+ notify?.('하니스가 묻는다 — 창에서 고르거나 Esc 로 답 없이 진행');
1320
+ const result = await pending;
1321
+ if (!result || result.cancelled) return '';
1316
1322
  const answer = result.answers[id];
1317
1323
  const selectedLabel = Array.isArray(answer) ? answer[0] : answer;
1318
1324
  const optionIndex = clarification.options.findIndex((option) => option.label === selectedLabel);
@@ -1393,8 +1399,14 @@ async function fetchLoopsDaemonJson<T>(url: string, headers: Record<string, stri
1393
1399
  }
1394
1400
  }
1395
1401
 
1396
- export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn> {
1402
+ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps, decideLedger?: DecisionLedger): SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn> {
1397
1403
  const registry = new SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn>();
1404
+ const decide = createDecideSlashHandler(decideLedger);
1405
+ registry.register(['decide', 'dec'], (args, ctx) => {
1406
+ // showDashboard in src/dashboard/index.ts dispatches into this registry.
1407
+ for (const line of decide(args).split('\n')) ctx.pushChatLine(line);
1408
+ ctx.setChatScrollOffset(-1);
1409
+ });
1398
1410
 
1399
1411
  registry.register(['now'], (args, ctx) => {
1400
1412
  for (const line of tuiNowSlash(args, nowDeps)) ctx.pushChatLine(line);
@@ -2436,7 +2448,11 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
2436
2448
  print: (line) => { for (const part of line.split('\n')) ctx.chatLines.push(` ${part}`); },
2437
2449
  log: (event, data, level) => debug.log('dev-pipeline', event, { ...data, surface: 'tui-slash' }, { level }),
2438
2450
  readLine: async () => '',
2439
- readClarification: (clarification) => readDashboardAskClarification(clarification, ctx.surfaceUx),
2451
+ readClarification: (clarification) => readDashboardAskClarification(clarification, ctx.surfaceUx, (line) => {
2452
+ ctx.pushChatLine(line);
2453
+ ctx.setChatScrollOffset(-1);
2454
+ ctx.draw();
2455
+ }),
2440
2456
  readFile: (file) => readFileSync(file, 'utf8'),
2441
2457
  writeFile: (file, data) => writeFileSync(file, data, 'utf8'),
2442
2458
  cwd: () => process.cwd(),
@@ -6161,46 +6177,57 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
6161
6177
  });
6162
6178
 
6163
6179
  registry.register(['model', 'm'], (args, ctx) => {
6164
- // /model — 현재 모델 + 스위치 가능 목록
6165
- // /model <alias> — 아래 curated 목록의 alias
6180
+ // /model — 지금 모델(사람 낱말) + 고르기
6181
+ // /model 빠름|보통|깊음 — 지금 provider 의 budget·better·best 티어 (model-choices.ts)
6182
+ // /model <코드명> — codex·terra·sol·luna·opus·sonnet·grok (그대로 동작)
6166
6183
  // effort(low/med/high)는 /reasoning. NL 미지원(대표 OK·slash-only).
6167
- //
6168
- // 명시 target 맵(id + provider) — catalog/prefix 추론 대신 확정값. 핵심:
6169
- // Codex 계열(gpt-5.5/5.6-sol/terra/luna)은 gpt- prefix 라 자동 추론은
6170
- // 'openai'(Chat Completions)로 오분류하나, 실제론 Responses API 필수라
6171
- // provider=openai-codex 여야 한다(omni-crawl 2026-07-17 확인). GPT(openai·
6172
- // Chat Completions)는 노출하지 않는다(대표 지시 — 혼란 방지·codex만).
6173
- // 비-OpenAI fallback 은 claude(anthropic)·grok.
6174
- const MODEL_TARGETS: Record<string, { model: string; provider: LLMProviderName }> = {
6175
- codex: { model: 'gpt-5.5', provider: 'openai-codex' },
6176
- // 🩸 2026-09-23 — GPT-6 에는 terra 가 없다(결정). 별칭은 남기되 codex 사다리 better 칸(sol 한 칸 아래 자리)을 가리킨다.
6177
- terra: { model: lookupLlmTierSpec('openai-codex', 'better').model, provider: 'openai-codex' },
6178
- sol: { model: lookupLlmTierSpec('openai-codex', 'best').model, provider: 'openai-codex' },
6179
- luna: { model: lookupLlmTierSpec('openai-codex', 'budget').model, provider: 'openai-codex' },
6180
- opus: { model: 'claude-opus-4-8', provider: 'anthropic' },
6181
- sonnet: { model: 'claude-sonnet-5', provider: 'anthropic' },
6182
- // ⭐ 티어 표를 따른다(옆 luna 와 같은 형태) — 하드코딩이면 표를 바꿔도 «안 따라온다»(2026-08-18 대표 4.6 재편)
6183
- grok: { model: lookupLlmTierSpec('grok', 'best').model, provider: 'grok' },
6184
- };
6185
6184
  const arg = (args[0] ?? '').toLowerCase().trim();
6186
6185
  const curCfg = getUserConfig();
6187
- const curModel = curCfg.llm.model ?? '(provider default)';
6188
- const aliases = Object.keys(MODEL_TARGETS);
6186
+ const curProvider = curCfg.llm.provider;
6187
+ const curModel = curCfg.llm.model;
6188
+ const curReasoning = effectiveReasoningLevel(curCfg.llm, curProvider, curModel);
6189
6189
  if (!arg) {
6190
- ctx.chatLines.push(`model: ${curModel} · provider ${curCfg.llm.provider}`);
6191
- ctx.chatLines.push(ctx.muted(` switch: /model <${aliases.join('|')}> · effort: /reasoning`));
6190
+ const [now, codes] = modelOverviewLines(curProvider, curModel, curReasoning);
6191
+ ctx.chatLines.push(now);
6192
+ ctx.chatLines.push(ctx.muted(codes));
6192
6193
  ctx.setChatScrollOffset(-1);
6193
6194
  return;
6194
6195
  }
6195
- const target = MODEL_TARGETS[arg];
6196
- if (!target) {
6197
- ctx.chatLines.push(ctx.muted(`/model: unknown "${arg}". try ${aliases.join('/')}.`));
6196
+ const resolution = resolveModelChoice(arg, curProvider);
6197
+ if (resolution.kind === 'needs-provider') {
6198
+ debug.log('dashboard.model', 'human-word-needs-provider', { word: resolution.word, provider: curProvider });
6199
+ ctx.chatLines.push(ctx.muted(`/model ${resolution.word}: provider 가 auto 라 고를 사다리가 없다 — /provider 로 먼저 하나를 고르거나 코드명(${MODEL_CODE_NAMES.join('/')})을 쓴다.`));
6198
6200
  ctx.setChatScrollOffset(-1);
6199
6201
  return;
6200
6202
  }
6201
- saveUserConfig({ ...curCfg, llm: { ...curCfg.llm, provider: target.provider, model: target.model } });
6203
+ if (resolution.kind === 'unknown') {
6204
+ ctx.chatLines.push(ctx.muted(`/model: "${arg}" 를 모른다. ${HUMAN_MODEL_WORDS.join(' | ')} 또는 ${MODEL_CODE_NAMES.join('/')}.`));
6205
+ ctx.setChatScrollOffset(-1);
6206
+ return;
6207
+ }
6208
+ const { target } = resolution;
6209
+ // A human word picks a whole tier (model ⊕ reasoning, like /reasoning it clears the codex override);
6210
+ // a code name changes the model only.
6211
+ const tierReasoning = resolution.kind === 'human' ? resolution.reasoningLevel : undefined;
6212
+ saveUserConfig({
6213
+ ...curCfg,
6214
+ llm: {
6215
+ ...curCfg.llm,
6216
+ provider: target.provider,
6217
+ model: target.model,
6218
+ ...(tierReasoning ? { reasoningLevel: tierReasoning, codexReasoning: undefined } : {}),
6219
+ },
6220
+ });
6202
6221
  reloadUserConfig();
6203
- ctx.chatLines.push(`model: ${curModel} → ${target.model} (provider ${target.provider})`);
6222
+ if (tierReasoning) ctx.refreshReasoningHudSegment();
6223
+ debug.log('dashboard.model', 'switch', {
6224
+ via: resolution.kind,
6225
+ choice: resolution.kind === 'human' ? resolution.tier : resolution.name,
6226
+ from: { provider: curProvider, model: curModel ?? null },
6227
+ to: target,
6228
+ reasoning: tierReasoning ?? null,
6229
+ });
6230
+ ctx.chatLines.push(modelChangeLine({ provider: curProvider, model: curModel, reasoning: curReasoning }, resolution));
6204
6231
  ctx.chatLines.push(ctx.muted(' └─ 다음 턴부터 반영 · 실행 뱃지(#4442)로 확인'));
6205
6232
  ctx.setChatScrollOffset(-1);
6206
6233
  });
@@ -6453,7 +6480,7 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
6453
6480
  { getStatusLines: ctx.getStatusLines ?? (() => []) },
6454
6481
  );
6455
6482
  if (!result) {
6456
- ctx.chatLines.push(ctx.warning(' /status takes no arguments'));
6483
+ ctx.chatLines.push(ctx.warning(' /status 는 인자 없이 · 자세히는 /status --debug'));
6457
6484
  ctx.setChatScrollOffset(-1);
6458
6485
  return;
6459
6486
  }
@@ -0,0 +1,60 @@
1
+ import { isIrreversible, renderCardText } from '../../decisions/decision-cards.js';
2
+ import { DecisionLedger, type DecisionEntry } from '../../decisions/decision-ledger.js';
3
+
4
+ function ageOf(entry: DecisionEntry, now: Date): string {
5
+ const raised = Date.parse(entry.raisedAt ?? entry.importedAt ?? '');
6
+ if (!Number.isFinite(raised)) return '나이 미상';
7
+ const minutes = Math.max(0, Math.floor((now.getTime() - raised) / 60_000));
8
+ if (minutes < 60) return `${minutes}분 전`;
9
+ if (minutes < 1440) return `${Math.floor(minutes / 60)}시간 전`;
10
+ return `${Math.floor(minutes / 1440)}일 전`;
11
+ }
12
+
13
+ /** One handler per dashboard registry: number-to-id mapping refreshes on list open or a successful answer. */
14
+ export function createDecideSlashHandler(ledger: DecisionLedger = new DecisionLedger(), now: () => Date = () => new Date()) {
15
+ let snapshot: DecisionEntry[] | undefined;
16
+ let pendingConfirm: { id: string; key: string } | undefined;
17
+ const openList = (): string => {
18
+ pendingConfirm = undefined;
19
+ snapshot = ledger.list({ status: 'open' });
20
+ if (!snapshot.length) return '열린 결정 없음';
21
+ return [...snapshot.map((entry, index) =>
22
+ `${index + 1}. ${entry.title} [${entry.options.map((option) => option.key).join('/')}] · ${entry.raisedBy.track ?? entry.raisedBy.agent} · ${ageOf(entry, now())}`),
23
+ '답하기: /decide <번호> <선택지 키>'].join('\n');
24
+ };
25
+ return (args: readonly string[]): string => {
26
+ if (!args.length) return openList();
27
+
28
+ const numbers = snapshot?.length ? `1~${snapshot.length}` : '없음 (/decide 로 목록 열기)';
29
+ const invalidNumber = `쓸 수 있는 번호: ${numbers}`;
30
+ const index = /^(?:[1-9]\d*)$/.test(args[0] ?? '') ? Number(args[0]) - 1 : -1;
31
+ const selected = snapshot?.[index];
32
+ if (!selected || !Number.isSafeInteger(index)) return invalidNumber;
33
+ const entry = ledger.list({ status: 'open' }).find((card) => card.id === selected.id);
34
+ if (!entry) return `이미 닫힌 결정입니다 (${args[0]}) — /decide 로 목록을 다시 여세요`;
35
+ if (args.length === 1) {
36
+ pendingConfirm = undefined;
37
+ return renderCardText(entry);
38
+ }
39
+
40
+ const key = args[1];
41
+ const option = entry.options.find((item) => item.key === key);
42
+ const keys = entry.options.length ? entry.options.map((item) => item.key).join(', ') : '없음';
43
+ if (!option || args.length > 3 || (args.length === 3 && args[2] !== '확인')) return `쓸 수 있는 선택지 키: ${keys}`;
44
+ if (isIrreversible(entry)) {
45
+ if (args[2] !== '확인' || pendingConfirm?.id !== entry.id || pendingConfirm.key !== key) {
46
+ pendingConfirm = { id: entry.id, key: option.key };
47
+ return `${renderCardText(entry, { confirm: option.key })}\n/decide ${args[0]} ${option.key} 확인`;
48
+ }
49
+ pendingConfirm = undefined;
50
+ }
51
+ let acknowledgement: string;
52
+ try {
53
+ const { delivery } = ledger.decideWithDelivery(entry.id, option.key, { kind: 'human' });
54
+ acknowledgement = `✓ ${entry.title} → ${option.label}${delivery && !delivery.ok ? ` · 런 답 전달 실패: decisions retry-answer ${entry.id}` : ''}`;
55
+ } catch {
56
+ return `결정을 기록하지 못했습니다 (${args[0]}) — /decide 로 목록을 다시 여세요`;
57
+ }
58
+ return `${acknowledgement}\n${openList()}`;
59
+ };
60
+ }
@@ -2,6 +2,7 @@ import type { SlashCommand } from '../../chat/index.js';
2
2
  import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
3
3
  import type { Role } from '../../maturity/feature-maturity.js';
4
4
  import { visibleWidth } from '../../tui.js';
5
+ import { HELP_GROUP_ORDER, helpGroupFor } from './help-groups.js';
5
6
 
6
7
  const ESSENTIAL_KEYS = [
7
8
  ['Enter', 'Send message'],
@@ -31,6 +32,11 @@ function fitLine(text: string, width: number): string {
31
32
  return fitted + '…';
32
33
  }
33
34
 
35
+ /** Head line for one help group (never starts with `/`, so it can't read as a command). */
36
+ export function helpGroupHeading(group: string): string {
37
+ return `▸ ${group}`;
38
+ }
39
+
34
40
  /** Plain content for the dashboard modal; registration, not the picker, decides which slash names exist. */
35
41
  export function buildEssentialHelpLines({
36
42
  names,
@@ -65,15 +71,22 @@ export function buildEssentialHelpLines({
65
71
  ? Math.max(1, Math.floor(width / (readableCellWidth + 1)))
66
72
  : 1;
67
73
  const columnWidth = Math.floor(width / columns);
74
+ // Task order (TUI-SLASH-DECIDE-NOW C): group head line, then that group's commands by name.
75
+ // Empty groups print no head; unmapped commands land in «그 밖», so none disappears.
68
76
  const commandLines: string[] = [];
69
- for (let start = 0; start < entries.length; start += columns) {
70
- const cells = entries.slice(start, start + columns).map(({ label, description }) => {
71
- const cellWidth = columnWidth - (columns > 1 ? 1 : 0);
72
- return visibleWidth(`${label} `) > cellWidth ? '' : fitLine(`${label} ${description}`, cellWidth);
73
- });
74
- commandLines.push(cells.map((cell, index) =>
75
- index === cells.length - 1 ? cell : cell + ' '.repeat(columnWidth - visibleWidth(cell)),
76
- ).join('').trimEnd());
77
+ for (const group of HELP_GROUP_ORDER) {
78
+ const members = entries.filter(({ name }) => helpGroupFor(name) === group);
79
+ if (members.length === 0) continue;
80
+ commandLines.push(fitLine(helpGroupHeading(group), width));
81
+ for (let start = 0; start < members.length; start += columns) {
82
+ const cells = members.slice(start, start + columns).map(({ label, description }) => {
83
+ const cellWidth = columnWidth - (columns > 1 ? 1 : 0);
84
+ return visibleWidth(`${label} `) > cellWidth ? '' : fitLine(`${label} ${description}`, cellWidth);
85
+ });
86
+ commandLines.push(cells.map((cell, index) =>
87
+ index === cells.length - 1 ? cell : cell + ' '.repeat(columnWidth - visibleWidth(cell)),
88
+ ).join('').trimEnd());
89
+ }
77
90
  }
78
91
  const keyItems = ESSENTIAL_KEYS.map(([key, description]) => `${key} ${description}`);
79
92
  const keyLines: string[] = [];
@@ -0,0 +1,44 @@
1
+ // TUI-SLASH-DECIDE-NOW C · 2026-10-07 — `/help` was one alphabetical block of ~70 commands (F3 · F14:
2
+ // `/model` sat three PgUps deep). Commands are now shown in task order: «시작», then the seven public
3
+ // verbs (내부 문서 `MAP-value-props-to-features` — same words), then «그 밖».
4
+ //
5
+ // The table maps a canonical slash name to its group. A registered command that is not in the table
6
+ // falls into «그 밖» automatically — grouping never hides a command (registration and maturity decide that).
7
+
8
+ export const HELP_START_GROUP = '시작';
9
+ export const HELP_OTHER_GROUP = '그 밖';
10
+
11
+ /** The seven public verbs, in the order the public map uses them. */
12
+ export const HELP_VERB_GROUPS = [
13
+ '지켜보기',
14
+ '조사하기',
15
+ '판단하기',
16
+ '만들기',
17
+ '실행하기',
18
+ '확인하기',
19
+ '기억하기',
20
+ ] as const;
21
+
22
+ /** Render order: start, the seven verbs, then everything else. */
23
+ export const HELP_GROUP_ORDER: readonly string[] = [HELP_START_GROUP, ...HELP_VERB_GROUPS, HELP_OTHER_GROUP];
24
+
25
+ const GROUP_MEMBERS: Readonly<Record<string, readonly string[]>> = {
26
+ [HELP_START_GROUP]: ['help', 'status', 'model', 'clear', 'new', 'resume'],
27
+ 지켜보기: ['now', 'loops', 'mission', 'log', 'term', 'surface', 'conv', 'capture', 'telegram', 'remaining', 'debug'],
28
+ 조사하기: ['research', 'prompt', 'context'],
29
+ 판단하기: ['decide', 'plan', 'wish', 'directive'],
30
+ 만들기: ['harness', 'ad', 'export'],
31
+ 실행하기: ['run-skill', 'claude', 'codex', 'gemini', 'acp', 'agent-room', 'showroom', 'relay', 'lane', 'reply', 'handoff', 'inject', 'control', 'qc', 'default'],
32
+ 확인하기: ['delta', 'design', 'rewind'],
33
+ 기억하기: ['memory', 'session', 'fork', 'resume-turn', 'history', 'persona'],
34
+ };
35
+
36
+ /** Canonical slash name → group. */
37
+ export const HELP_GROUP_BY_NAME: ReadonlyMap<string, string> = new Map(
38
+ Object.entries(GROUP_MEMBERS).flatMap(([group, names]) => names.map((name) => [name, group] as const)),
39
+ );
40
+
41
+ /** Group for a canonical slash name; anything unmapped is «그 밖». */
42
+ export function helpGroupFor(name: string): string {
43
+ return HELP_GROUP_BY_NAME.get(name) ?? HELP_OTHER_GROUP;
44
+ }