elanous 0.2.25 → 0.2.28

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 (337) 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/chunks/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -78,6 +78,8 @@ type AxisUsageRow = {
78
78
  unknownCostCalls: number;
79
79
  includedCalls: number;
80
80
  apiEquivalentUsd: number;
81
+ /** 비용 순위 = usdKnown + apiEquivalentUsd — 구독 호출의 목록가 «그림자 비용»까지 더한 값. */
82
+ rankUsd: number;
81
83
  };
82
84
 
83
85
  function usageData(row: LogStoreRow, unwrapPod = false): ExtendedRunUsageInput | null {
@@ -115,6 +117,8 @@ function rollupByAxis(inputs: readonly ExtendedRunUsageInput[], axis: UsageAxis)
115
117
  }
116
118
  return [...groups].map(([value, group]) => {
117
119
  const rows = rollupRunUsage(group);
120
+ const usdKnown = rows.reduce((sum, row) => sum + row.usdKnown, 0);
121
+ const apiEquivalentUsd = rows.reduce((sum, row) => sum + row.apiEquivalentUsd, 0);
118
122
  const result: AxisUsageRow = {
119
123
  [axis]: value,
120
124
  hostIds: [...new Set(rows.flatMap((row) => row.hostIds))],
@@ -124,13 +128,14 @@ function rollupByAxis(inputs: readonly ExtendedRunUsageInput[], axis: UsageAxis)
124
128
  cacheReadInputTokens: rows.reduce((sum, row) => sum + row.cacheReadInputTokens, 0),
125
129
  cacheCreationInputTokens: rows.reduce((sum, row) => sum + row.cacheCreationInputTokens, 0),
126
130
  reasoningOutputTokens: rows.reduce((sum, row) => sum + row.reasoningOutputTokens, 0),
127
- usdKnown: rows.reduce((sum, row) => sum + row.usdKnown, 0),
131
+ usdKnown,
128
132
  unknownCostCalls: rows.reduce((sum, row) => sum + row.unknownCostCalls, 0),
129
133
  includedCalls: rows.reduce((sum, row) => sum + row.includedCalls, 0),
130
- apiEquivalentUsd: rows.reduce((sum, row) => sum + row.apiEquivalentUsd, 0),
134
+ apiEquivalentUsd,
135
+ rankUsd: usdKnown + apiEquivalentUsd,
131
136
  };
132
137
  return result;
133
- }).sort((a, b) => b.calls - a.calls);
138
+ }).sort((a, b) => (b.rankUsd - a.rankUsd) || (b.calls - a.calls));
134
139
  }
135
140
 
136
141
  function isUsageAxis(value: string): value is 'run' | 'role' | UsageAxis {
@@ -139,7 +144,7 @@ function isUsageAxis(value: string): value is 'run' | 'role' | UsageAxis {
139
144
 
140
145
  function formatAxisUsage(row: AxisUsageRow, axis: UsageAxis): string {
141
146
  const value = row[axis] ?? '(none)';
142
- return `${axis}=${value} hosts=${row.hostIds.join(',') || '(none)'} calls=${row.calls} input=${row.inputTokens} output=${row.outputTokens} cacheRead=${row.cacheReadInputTokens} cacheCreation=${row.cacheCreationInputTokens} reasoning=${row.reasoningOutputTokens} usdKnown=${row.usdKnown} unknownCostCalls=${row.unknownCostCalls} includedCalls=${row.includedCalls} apiEquivalentUsd=${row.apiEquivalentUsd}`;
147
+ return `${axis}=${value} hosts=${row.hostIds.join(',') || '(none)'} calls=${row.calls} input=${row.inputTokens} output=${row.outputTokens} cacheRead=${row.cacheReadInputTokens} cacheCreation=${row.cacheCreationInputTokens} reasoning=${row.reasoningOutputTokens} usdKnown=${row.usdKnown} unknownCostCalls=${row.unknownCostCalls} includedCalls=${row.includedCalls} apiEquivalentUsd=${row.apiEquivalentUsd} rankUsd=${row.rankUsd}`;
143
148
  }
144
149
 
145
150
  function defaultCreditPlan(): CodexCreditPlan | null {
@@ -0,0 +1,40 @@
1
+ import type { ContextCard } from './index.js';
2
+
3
+ export interface CellCardItem {
4
+ readonly id: string;
5
+ readonly title: string;
6
+ readonly status: string;
7
+ readonly owner?: string | null;
8
+ readonly disposition?: string;
9
+ readonly evidence?: string;
10
+ }
11
+
12
+ const POINTER_PATTERN = /#\d+|run-[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/giu;
13
+
14
+ function extractPointers(text: string): string[] {
15
+ return [...text.matchAll(POINTER_PATTERN)].map(([pointer]) => pointer);
16
+ }
17
+
18
+ /** Build a compact checklist-cell card; evidence is used only to derive stable source pointers. */
19
+ export function buildCellCard(version: string, item: CellCardItem, asOf: string): ContextCard {
20
+ const pointers = [...new Set([
21
+ ...extractPointers(item.evidence ?? ''),
22
+ ...extractPointers(item.title),
23
+ `checklist:${version}/${item.id}`,
24
+ ])];
25
+ const reasons = [
26
+ item.owner ? `담당 ${item.owner}` : undefined,
27
+ item.disposition ? `처분 ${item.disposition}` : undefined,
28
+ ].filter((value): value is string => value !== undefined);
29
+ return {
30
+ id: `cell:${version}/${item.id}`,
31
+ kind: 'cell',
32
+ conclusion: (item.title.split(/[\r\n\u2028\u2029]/u, 1)[0] ?? '').trim(),
33
+ why: reasons.join(' · '),
34
+ verdict: item.status,
35
+ remaining: [],
36
+ pointers,
37
+ source: `release-checklist:${version}`,
38
+ updatedAt: asOf,
39
+ };
40
+ }
@@ -0,0 +1,35 @@
1
+ import { debug } from '../debug/log.js';
2
+
3
+ export interface BootTimer {
4
+ begin(): void;
5
+ phase(name: string): void;
6
+ done(): void;
7
+ }
8
+
9
+ export function createBootTimer(now: () => number = performance.now): BootTimer {
10
+ const startedAt = now();
11
+ let phaseStartedAt = startedAt;
12
+ let lastPhase: string | undefined;
13
+ let slowest: { phase: string; ms: number } | undefined;
14
+ let completed = false;
15
+
16
+ return {
17
+ begin(): void {
18
+ phaseStartedAt = now();
19
+ },
20
+ phase(name: string): void {
21
+ const current = now();
22
+ const ms = current - phaseStartedAt;
23
+ phaseStartedAt = current;
24
+ lastPhase = name;
25
+ if (!slowest || ms > slowest.ms) slowest = { phase: name, ms };
26
+ try { debug.log('tui.boot', 'phase', { phase: name, ms }); } catch { /* logging must not block startup */ }
27
+ },
28
+ done(): void {
29
+ if (completed) return;
30
+ completed = true;
31
+ const totalMs = now() - startedAt;
32
+ try { debug.log('tui.boot', 'summary', { totalMs, slowest: slowest ?? (lastPhase ? { phase: lastPhase, ms: totalMs } : null) }); } catch { /* logging must not block startup */ }
33
+ },
34
+ };
35
+ }
@@ -106,6 +106,8 @@ export interface FirstScreenBandInput {
106
106
  readonly width: number;
107
107
  readonly daemon: boolean;
108
108
  readonly link?: TuiDaemonLink;
109
+ /** Renders the three available-action rows and the example row in this locale; `undefined` keeps the historical band. */
110
+ readonly locale?: 'ko' | 'en';
109
111
  readonly pwa?: {
110
112
  readonly tailnet?: string | null;
111
113
  readonly lan?: string | null;
@@ -138,10 +140,37 @@ function fitCells(text: string, limit: number): string {
138
140
  return result;
139
141
  }
140
142
 
143
+ // First-run guidance: three rows of available actions plus one immediately-typeable
144
+ // example row, per locale. Every row names something the empty TUI can really do.
145
+ const GUIDANCE: Record<'ko' | 'en', {
146
+ readonly actions: readonly [string, string, string];
147
+ readonly example: string;
148
+ }> = {
149
+ ko: {
150
+ actions: [
151
+ '· 대화 — 무엇이든 자연어로 물어보고 시켜보세요',
152
+ '· 명령 — /help 로 전체 명령을 보세요',
153
+ '· 스킬 — /run-skill <이름> 으로 설치된 스킬을 돌리세요',
154
+ ],
155
+ example: '바로 입력해 보세요: 무엇을 할 수 있어?',
156
+ },
157
+ en: {
158
+ actions: [
159
+ '· Chat — ask for anything in plain language',
160
+ '· Commands — /help lists every command',
161
+ '· Skills — /run-skill <name> runs an installed skill',
162
+ ],
163
+ example: 'Try typing: what can you do?',
164
+ },
165
+ };
166
+
141
167
  /** A startup transcript band; never includes credentials or a partial address. */
142
168
  export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
143
169
  const width = Number.isFinite(input.width) ? Math.max(0, Math.floor(input.width)) : 0;
144
- const fit = (text: string): string => text.slice(0, width);
170
+ // The historical band is mixed: the link row speaks Korean, the remaining lines English.
171
+ // An explicit locale unifies them; `undefined` must keep every pinned line byte-identical.
172
+ const linkKorean = input.locale !== 'en';
173
+ const plainKorean = input.locale === 'ko';
145
174
  const address = [input.pwa?.tailnet, input.pwa?.lan, input.pwa?.loopback]
146
175
  .map((candidate) => {
147
176
  if (!candidate) return null;
@@ -154,32 +183,53 @@ export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
154
183
  }
155
184
  })
156
185
  .find((candidate) => candidate !== null);
157
- const fallback = input.pwa === undefined ? 'PWA: not checked' : 'PWA: unavailable';
186
+ const fallback = input.pwa === undefined
187
+ ? (plainKorean ? 'PWA: 확인 안 함' : 'PWA: not checked')
188
+ : (plainKorean ? 'PWA: 사용할 수 없음' : 'PWA: unavailable');
158
189
  const addressLine = address ? `PWA: ${address}` : fallback;
159
190
 
160
191
  const daemonLine = (() => {
161
192
  const link = input.link;
162
- if (!link) return `elanous | daemon: ${input.daemon ? 'online' : 'offline'}`;
163
- if (link.status === 'checking') return 'elanous | 데몬: 확인 중';
193
+ if (!link) return plainKorean
194
+ ? `elanous | 데몬: ${input.daemon ? '켜짐' : '꺼짐'}`
195
+ : `elanous | daemon: ${input.daemon ? 'online' : 'offline'}`;
196
+ if (link.status === 'checking') return linkKorean ? 'elanous | 데몬: 확인 중' : 'elanous | daemon: checking';
164
197
  if (link.status === 'connected') {
165
198
  let address: string;
166
199
  try {
167
200
  const url = new URL(link.address);
168
201
  address = ['http:', 'https:'].includes(url.protocol) && url.hostname ? url.origin : '';
169
202
  } catch { address = ''; }
170
- const line = `elanous | 데몬: 연결됨${address ? ` (${address})` : ''}`;
171
- return cellWidth(line) <= width ? line : 'elanous | 데몬: 연결됨';
203
+ const bare = linkKorean ? 'elanous | 데몬: 연결됨' : 'elanous | daemon: connected';
204
+ const line = `${bare}${address ? ` (${address})` : ''}`;
205
+ return cellWidth(line) <= width ? line : bare;
206
+ }
207
+ if (link.status === 'error') {
208
+ return linkKorean ? `elanous | 데몬: ${link.stage} 조회 오류` : `elanous | daemon: ${link.stage} lookup error`;
172
209
  }
173
- if (link.status === 'error') return `elanous | 데몬: ${link.stage} 조회 오류`;
174
- const bare = `elanous | 데몬: ${link.status === 'absent' ? '없음' : '응답 없음'}`;
210
+ const bare = linkKorean
211
+ ? `elanous | 데몬: ${link.status === 'absent' ? '없음' : '응답 없음'}`
212
+ : `elanous | daemon: ${link.status === 'absent' ? 'absent' : 'unhealthy'}`;
175
213
  if (!link.startCommand || CONTROL_CHARS.test(link.startCommand)) return bare;
176
- const line = `${bare} · 시작: ${link.startCommand}`;
214
+ const line = `${bare} · ${linkKorean ? '시작' : 'start'}: ${link.startCommand}`;
177
215
  return cellWidth(line) <= width ? line : bare;
178
216
  })();
179
217
 
180
- return [
181
- input.link ? fitCells(daemonLine, width) : fit(daemonLine),
182
- addressLine.length <= width ? addressLine : fit(address ? 'PWA: address available' : fallback),
183
- fit('Type a message to begin | /help for commands'),
218
+ const hintLine = 'Type a message to begin | /help for commands';
219
+
220
+ const statusRows = [
221
+ fitCells(daemonLine, width),
222
+ cellWidth(addressLine) <= width
223
+ ? addressLine
224
+ : fitCells(address ? (plainKorean ? 'PWA: 주소 확인됨' : 'PWA: address available') : fallback, width),
184
225
  ];
226
+ // The historical band folds its guidance into one mixed-language hint row.
227
+ // An explicit locale renders the same guidance as three available-action rows
228
+ // plus one immediately-typeable example row, in that locale — on every screen
229
+ // state (empty, checking, absent, unhealthy, error) because none of these
230
+ // rows depends on the daemon link or the PWA lookup.
231
+ const guidanceRows = input.locale
232
+ ? [...GUIDANCE[input.locale].actions, GUIDANCE[input.locale].example].map((row) => fitCells(row, width))
233
+ : [fitCells(hintLine, width)];
234
+ return [...statusRows, ...guidanceRows];
185
235
  }
@@ -643,6 +643,7 @@ import { activeCodexAccountView } from '../oauth/codex-account-store.js';
643
643
  import { buildDashboardStatusLines } from './dashboard-status-lines.js';
644
644
  import { readSlashContextNow, seatsNowLine } from '../context-bus/context-now-surfaces.js';
645
645
  import { buildFirstScreenBand, resolveTuiDaemonLink, type TuiDaemonLink } from './first-screen-band.js';
646
+ import { detectLocale } from '../expression/locale.js';
646
647
  import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
647
648
  import {
648
649
  workingDirSegment, sessionCwdSegment, modelSegment, gitSegment,
@@ -965,6 +966,7 @@ import {
965
966
  enterSessionControlMode,
966
967
  exitSessionControlMode,
967
968
  isSessionControlActive,
969
+ routeDevHarnessSubmitIntent,
968
970
  parseSessionControlSlash,
969
971
  parseSessionSurfaceSlash,
970
972
  resolveSessionInputModeFromChatMode,
@@ -1023,7 +1025,7 @@ import { expandPromptReferences } from '../prompt/references.js';
1023
1025
  import { spawnCodingAgent, CodingAgentBinaryMissing } from '../terminal/coding-agent.js';
1024
1026
  import type { TerminalSession } from '../terminal/session-registry.js';
1025
1027
  import { createSessionPickerModal } from '../session/picker-modal.js';
1026
- import { listSessions as listChatSessionsForResume } from '../session/index.js';
1028
+ import { listSessions as listChatSessionsForResume, adoptSession as adoptChatSession, setActiveSessionId as markActiveChatSession } from '../session/index.js';
1027
1029
  // TUI 부활 S-b — 타임트래블 포크 엔진 (codex ForkSnapshot 동형 · 2026-07-09
1028
1030
  // 이식 후 호출처 0 이던 방치 자산 회수). 순수 히스토리 절단 로직만 소비;
1029
1031
  // 영속은 세션 스토어(forkSessionFromHistory)가 담당한다.
@@ -1179,6 +1181,20 @@ import { buildDebugCallStack } from '../debug/call-stack.js';
1179
1181
  import { buildAstGrepHostTool } from '../skills/tools/ast-grep.js';
1180
1182
  import { buildLspHostTool } from '../skills/tools/lsp/index.js';
1181
1183
  import { PluginHost } from '../plugins/core/host.js';
1184
+
1185
+ /** Register the same L2 host tools used by the live TUI before each turn's catalog is assembled. */
1186
+ export function registerDashboardCoreHostTools(host: Pick<PluginHost, 'registerHostTool'>): void {
1187
+ const coreMod = require('../domains/core-tools.js') as typeof import('../domains/core-tools.js');
1188
+ const core = coreMod.buildCoreTools();
1189
+ for (const spec of core.specs) {
1190
+ host.registerHostTool({
1191
+ name: spec.name,
1192
+ description: spec.description,
1193
+ parameters: spec.parameters,
1194
+ handler: async (args) => core.dispatch(spec.name, args as Record<string, unknown>),
1195
+ });
1196
+ }
1197
+ }
1182
1198
  import { registerDashboardPaneHostTools } from './panes/host-tools.js';
1183
1199
  import { DEFAULT_THEME_TOKENS, mergeThemeTokens, resolveThemeTokens, type ThemeTokenInput, type ThemeTokens } from '../theme/tokens.js';
1184
1200
  import { resolveActiveTheme, setActivePresetInConfig } from './render/theme-resolver.js';
@@ -1407,6 +1423,8 @@ const isPluginBusy = (host: { active(): { plugin: { isBusy?: (s: any) => boolean
1407
1423
  // ── Main dashboard ──
1408
1424
 
1409
1425
  export interface ShowDashboardOptions {
1426
+ /** Internal startup timing signal for the first successfully flushed dashboard frame. */
1427
+ onFirstFrameRendered?: () => void;
1410
1428
  /** Force debug sinks on at launch (file + mirror). Equivalent to
1411
1429
  * typing `/debug on` immediately after startup. Paired with
1412
1430
  * chatOnly=true by the `elanous --debug` CLI flag so every traced
@@ -1501,6 +1519,16 @@ export function buildDashboardTurnTypeaheadPromptZone(input: TurnTypeaheadPrompt
1501
1519
  };
1502
1520
  }
1503
1521
 
1522
+ /** The chat-main submit branch calls this before any model dispatch. Keeping
1523
+ * the decision here lets the dashboard test execute the same entry point. */
1524
+ export function resolveDashboardHarnessSubmitIntent(
1525
+ text: string,
1526
+ options: Parameters<typeof resolveDashboardChatMainSubmitIntent>[1],
1527
+ resolveIntent: typeof resolveDashboardChatMainSubmitIntent = resolveDashboardChatMainSubmitIntent,
1528
+ ) {
1529
+ return routeDevHarnessSubmitIntent(resolveIntent(text, options));
1530
+ }
1531
+
1504
1532
  /** 프롬프트 zone 에 관측만 덧입힌다 — 렌더 산출은 `inner` 가 소유한다.
1505
1533
  *
1506
1534
  * ⭐ **왜 뽑았나**(무인 리뷰 must-fix · 2026-08-01): 종전엔 이 래퍼가 `zones.push({...})` 안에
@@ -1567,6 +1595,7 @@ export interface DashboardFrameInput {
1567
1595
  promptCaret: CursorState | null;
1568
1596
  suppressPromptArea: boolean;
1569
1597
  onEssentialFrameCursorDecision?: (observation: EssentialFrameCursorObservation) => void;
1598
+ onFrameWriteComplete?: (error?: Error | null) => void;
1570
1599
  }
1571
1600
 
1572
1601
  export interface EssentialFrameCursorObservation {
@@ -1612,6 +1641,7 @@ export function renderDashboardFrame(
1612
1641
  overlay: input.overlay,
1613
1642
  force: input.force,
1614
1643
  cursor: paintCursor(essentialDecision.cursor ?? input.coordinatorCursor ?? null),
1644
+ onWriteComplete: input.onFrameWriteComplete,
1615
1645
  });
1616
1646
  input.onEssentialFrameCursorDecision?.({
1617
1647
  caret: essentialDecision.cursor,
@@ -1867,18 +1897,20 @@ export function renderDashboardFirstScreenBand(input: {
1867
1897
  readonly history: ReadonlyArray<{ role: string }>;
1868
1898
  readonly lines: string[];
1869
1899
  readonly width: number;
1900
+ /** Renders the localized guidance rows; `undefined` keeps the historical mixed band. */
1901
+ readonly locale?: 'ko' | 'en';
1870
1902
  readonly draw: () => void;
1871
1903
  readonly observe: (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }) => void;
1872
1904
  readonly show?: () => Promise<Pick<NexusShowResult, 'status' | 'urls'>>;
1873
1905
  readonly daemonLink?: () => Promise<TuiDaemonLink>;
1874
1906
  }): Promise<void> {
1875
1907
  if (input.history.some((message) => message.role !== 'system')) return Promise.resolve();
1876
- const band = buildFirstScreenBand({ width: input.width, daemon: false, link: { status: 'checking' } });
1908
+ const band = buildFirstScreenBand({ width: input.width, daemon: false, link: { status: 'checking' }, locale: input.locale });
1877
1909
  input.lines.unshift(...band);
1878
1910
  const daemonLink = input.daemonLink ?? resolveTuiDaemonLink;
1879
1911
  const linkPending = Promise.resolve().then(() => daemonLink()).catch((): TuiDaemonLink => ({ status: 'error', stage: 'health' })).then((link) => {
1880
1912
  if (input.lines[0] !== band[0]) return;
1881
- input.lines[0] = buildFirstScreenBand({ width: input.width, daemon: false, link })[0]!;
1913
+ input.lines[0] = buildFirstScreenBand({ width: input.width, daemon: false, link, locale: input.locale })[0]!;
1882
1914
  try { input.draw(); } catch { /* screen may have closed */ }
1883
1915
  });
1884
1916
  const show = input.show ?? (() => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } }));
@@ -1892,7 +1924,7 @@ export function renderDashboardFirstScreenBand(input: {
1892
1924
  const hasTailnet = !!pwa?.tailnet;
1893
1925
  observe({ daemon, hasPwa, hasTailnet });
1894
1926
  if (input.lines[1] !== band[1]) return;
1895
- input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa })[1]!;
1927
+ input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa, locale: input.locale })[1]!;
1896
1928
  try { input.draw(); } catch { /* screen may have closed */ }
1897
1929
  }).catch(() => {
1898
1930
  observe({ daemon: false, hasPwa: false, hasTailnet: false });
@@ -1901,6 +1933,7 @@ export function renderDashboardFirstScreenBand(input: {
1901
1933
  }
1902
1934
 
1903
1935
  export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<DashboardAction> {
1936
+ let firstDashboardFrameRendered = false;
1904
1937
  await awaitGlobalPersonaLoad().catch(() => {});
1905
1938
  const dashboardQuitConfirmationState = createQuitConfirmationState();
1906
1939
  const signalQuitConfirmationState = createQuitConfirmationState();
@@ -1910,7 +1943,11 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
1910
1943
  closeTui();
1911
1944
  };
1912
1945
  const exitDashboardTui = (): void => {
1913
- closeDashboardForExit({ closeTui: closeDashboardTui, confirmationState: dashboardQuitConfirmationState });
1946
+ closeDashboardForExit({
1947
+ closeTui: closeDashboardTui,
1948
+ confirmationState: dashboardQuitConfirmationState,
1949
+ getSessionId: (): string | undefined => acpSessionIdRef.current ?? undefined,
1950
+ });
1914
1951
  };
1915
1952
  // ⭐⭐⭐ `N1` — 신호 경로(`SIGINT`/`SIGTERM`)와 저수준 `Ctrl+C` 도 같은 종료를 타게 «등록»한다.
1916
1953
  // ⛔ 종전엔 그 둘만 세션 안내를 «못 받았다»(일곱 경로 중 다섯만 받았다).
@@ -1920,6 +1957,7 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
1920
1957
  closeTui: () => { /* 신호 경로: cleanup() 이 이미 복원했다 */ },
1921
1958
  confirmationState: signalQuitConfirmationState,
1922
1959
  forceQuit: true,
1960
+ getSessionId: (): string | undefined => acpSessionIdRef.current ?? undefined,
1923
1961
  });
1924
1962
  });
1925
1963
  // E3 (§7.4 TS baseline reduction · 2026-05-17) — forward declarations
@@ -3342,6 +3380,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
3342
3380
  // ⛔ `attachedSessionId`(대화 세션)와 «다른 축»이다 — 루프는 ACP 세션으로 스코프를 연다.
3343
3381
  // ⚠️ 세션은 이 함수 «뒤»에서 만들어지므로 참조로 둔다(호출 시점엔 채워져 있다).
3344
3382
  const acpSessionIdRef: { current: string | null } = { current: null };
3383
+ const adoptedAcpSessionIds = new Set<string>();
3384
+ let activeAcpSessionId: string | null = null;
3345
3385
  const activeSessionIdForTypeahead = (): string | null => acpSessionIdRef.current;
3346
3386
  const paintTurnTypeaheadEcho = (): void => {
3347
3387
  const { rows: tr, cols: tc } = termSize();
@@ -10014,16 +10054,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
10014
10054
  // family(전 surface 기본) — 이로써 TUI 에서도 "P2 왜 실패?" 를 엘라누스가 툴로 상황판단한다.
10015
10055
  // fail-soft: 코어 도구 로드 실패가 대시보드 부팅을 막지 않는다.
10016
10056
  try {
10017
- const coreMod = require('../domains/core-tools.js') as typeof import('../domains/core-tools.js');
10018
- const core = coreMod.buildCoreTools();
10019
- for (const spec of core.specs) {
10020
- pluginHost.registerHostTool({
10021
- name: spec.name,
10022
- description: spec.description,
10023
- parameters: spec.parameters,
10024
- handler: async (args) => core.dispatch(spec.name, args as Record<string, unknown>),
10025
- });
10026
- }
10057
+ registerDashboardCoreHostTools(pluginHost);
10027
10058
  } catch { /* fail-soft */ }
10028
10059
  // LSP (Phase L3 of 내부 문서 `ROADMAP-lsp-integration`) — structural /
10029
10060
  // symbol-level source queries via typescript-language-server. Pool
@@ -11530,6 +11561,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
11530
11561
  // ⭐ Single flush: resolve the prompt caret after overlays.
11531
11562
  const frameCursorDecision = display.cursorDecision();
11532
11563
  renderDashboardFrame(baseLines, composeDashboardFrameWithCoordinatorModal(display, {
11564
+ onFrameWriteComplete: firstDashboardFrameRendered ? undefined : (error) => {
11565
+ if (error || firstDashboardFrameRendered) return;
11566
+ firstDashboardFrameRendered = true;
11567
+ try { opts.onFirstFrameRendered?.(); } catch { /* timing must not break dashboard */ }
11568
+ },
11533
11569
  overlay: modalFrame,
11534
11570
  force: forceFromCoordinator || modalKey !== lastModalKey,
11535
11571
  cursorOwner: frameCursorDecision.owner,
@@ -15670,6 +15706,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15670
15706
  history: chat.history,
15671
15707
  lines: chatLines,
15672
15708
  width: Math.max(0, termSize().cols - 4),
15709
+ // The band ships ko/en only; any other detected locale keeps the English rows.
15710
+ locale: detectLocale() === 'ko' ? 'ko' : 'en',
15673
15711
  draw: () => draw(),
15674
15712
  observe: (data) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
15675
15713
  });
@@ -17422,7 +17460,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17422
17460
  // resource_link blocks so dropped screenshots and PDFs flow
17423
17461
  // naturally to the agent. debug.log gates the snapshot so
17424
17462
  // off-state stays zero-cost.
17425
- const submitIntent = resolveDashboardChatMainSubmitIntent(input.text, {
17463
+ const submitIntent = resolveDashboardHarnessSubmitIntent(input.text, {
17426
17464
  stickyBackend: dashboardAcpChat.getSticky(),
17427
17465
  source: input.externalSubmitSource,
17428
17466
  });
@@ -17443,7 +17481,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
17443
17481
  }
17444
17482
  if (submitIntent.kind === 'submit-turn' && submitIntent.route === 'sticky-acp') {
17445
17483
  await runDashboardChatMainSubmitIntent({
17446
- submit: createDashboardChatMainTurnSubmit(submitIntent),
17484
+ submit: createDashboardChatMainTurnSubmit({
17485
+ ...submitIntent,
17486
+ sessionId: submitIntent.sessionId ?? dashboardAcpSession.currentSessionId,
17487
+ }),
17447
17488
  beforeExecute: (submit) => {
17448
17489
  maybeEmitDashboardSubmitQuickPass({
17449
17490
  submit,
@@ -19186,16 +19227,19 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19186
19227
  do {
19187
19228
  const turnText = goalLoopUserText ?? tok.text;
19188
19229
  await runDashboardChatMainSubmitIntent({
19189
- submit: createDashboardChatMainTurnSubmit(
19190
- submitIntent.kind === 'submit-turn' && submitIntent.route === 'plain' && goalLoopUserText === null
19191
- ? submitIntent
19192
- : {
19193
- kind: 'submit-turn',
19194
- source: { kind: 'keyboard', surface: 'dashboard-chat-main' },
19195
- text: turnText,
19196
- route: goalLoopUserText === null ? 'plain' : 'plain',
19197
- },
19198
- ),
19230
+ submit: createDashboardChatMainTurnSubmit({
19231
+ ...(submitIntent.kind === 'submit-turn' && submitIntent.route === 'plain' && goalLoopUserText === null
19232
+ ? submitIntent
19233
+ : {
19234
+ kind: 'submit-turn' as const,
19235
+ source: { kind: 'keyboard' as const, surface: 'dashboard-chat-main' },
19236
+ text: turnText,
19237
+ route: 'plain' as const,
19238
+ }),
19239
+ sessionId: submitIntent.kind === 'submit-turn'
19240
+ ? submitIntent.sessionId ?? dashboardAcpSession.currentSessionId
19241
+ : dashboardAcpSession.currentSessionId,
19242
+ }),
19199
19243
  beforeExecute: (submit) => {
19200
19244
  maybeEmitDashboardSubmitQuickPass({
19201
19245
  submit,
@@ -19246,6 +19290,29 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19246
19290
  }
19247
19291
  } while (goalLoopUserText !== null);
19248
19292
 
19293
+ // SESSION-RESUME-ISOLATED: 완료된 턴의 ACP 세션을 원장에 등록하고 active 로 지정한다.
19294
+ // 서버의 완료-채택(persistCompletedTuiAcpTurn)은 origin_session 메타가 실렸을 때만
19295
+ // 도는데, 격리 우주 첫 턴에는 origin 세션이 없어 메타가 안 실린다 — 여기가 그 갭을 닫는다.
19296
+ const completedAcpSessionId = dashboardAcpSession.currentSessionId;
19297
+ if (completedAcpSessionId) {
19298
+ acpSessionIdRef.current = completedAcpSessionId;
19299
+ // 원격 데몬 세션의 원장은 데몬이 소유한다. 로컬 등록은 세션마다 한 번만 한다.
19300
+ if (!daemonAttached && activeAcpSessionId !== completedAcpSessionId) {
19301
+ try {
19302
+ if (!adoptedAcpSessionIds.has(completedAcpSessionId)) {
19303
+ adoptChatSession(completedAcpSessionId, { source: 'tui', transport: 'acp' });
19304
+ adoptedAcpSessionIds.add(completedAcpSessionId);
19305
+ }
19306
+ markActiveChatSession(completedAcpSessionId);
19307
+ activeAcpSessionId = completedAcpSessionId;
19308
+ } catch (error) {
19309
+ // 등록 실패가 대화 흐름을 끊지 않되, 다음 턴 재시도 전 원인을 남긴다.
19310
+ debug.log('session.adopt', 'failed', { sessionId: completedAcpSessionId, error: String(error) });
19311
+ pushDebugLine(C.warning(`session adopt failed: ${String(error)}`));
19312
+ }
19313
+ }
19314
+ }
19315
+
19249
19316
  continue; // stay in input mode after chat
19250
19317
  } // end while (input mode loop)
19251
19318
  // Disarm the prompt-repaint hook: textInput's drawAll
@@ -191,10 +191,15 @@ export async function runDashboardChatMainPlainTurn(
191
191
  // 「사다리가 안 걸린 것」과 「걸렸는데 키가 딴 데로 간 것」을 ***원리상 못 갈랐다***.
192
192
  // ⭐ 이 한 줄이 그 둘을 가른다 — 이 이벤트가 «있는데» typeahead 가 0이면 후자다.
193
193
  // ⛔ 행동을 바꾸지 않는다(관측만). 처방은 `RFC-tui-mid-turn-input-queue-2026-08-19` §2 Q1.
194
+ const startedAtDate = new Date(deps.turnStartedAt);
195
+ const turnStartedAtIso = Number.isFinite(startedAtDate.getTime())
196
+ ? startedAtDate.toISOString()
197
+ : undefined;
194
198
  try {
195
199
  deps.debugLog?.('chat-main.plain-turn', 'streaming-keys-attached', {
196
200
  userTextBytes: Buffer.byteLength(userText ?? '', 'utf8'),
197
201
  turnStartedAt: deps.turnStartedAt,
202
+ turnStartedAtIso,
198
203
  });
199
204
  } catch { /* fail-soft — 관측이 턴을 막지 않는다 */ }
200
205
  let stage = 'streaming-keys-attached';
@@ -291,11 +296,32 @@ export async function runDashboardChatMainPlainTurn(
291
296
  });
292
297
 
293
298
  if (finishIfAborted()) return;
299
+ // TUI-FIRST-TURN-STALL — 09:48 표본에서 llm.stream consume-start 뒤
300
+ // first-token 이벤트가 0건이어서 「첫 토큰까지의 구간」을 잴 표면이 없었다.
301
+ // turnStartedAt 를 재사용해 첫 비어 있지 않은 text chunk 에 정확히 한 번
302
+ // chat.turn/first-token 을 남긴다. 도구 호출이 먼저 흘러도 text 가 도착해야
303
+ // 기록되므로 request→first-token 구간은 그대로 측정된다. ISO 시각 좌표는
304
+ // pty record 및 TUI-BOOT-TIME(src/index.ts) 부팅 계측과의 대응용이다.
305
+ let firstTokenLogged = false;
306
+ const logFirstToken = (chunk: string): void => {
307
+ if (firstTokenLogged || !chunk || abortCtrl.signal.aborted) return;
308
+ firstTokenLogged = true;
309
+ const firstTokenAt = Date.now();
310
+ try {
311
+ deps.debugLog?.('chat.turn', 'first-token', {
312
+ ms: firstTokenAt - deps.turnStartedAt,
313
+ turnStartedAt: deps.turnStartedAt,
314
+ turnStartedAtIso,
315
+ firstTokenAtIso: new Date(firstTokenAt).toISOString(),
316
+ });
317
+ } catch { /* fail-soft — 관측이 턴을 막지 않는다 */ }
318
+ };
294
319
  const streamResult = await runDashboardPlainTurnStream({
295
320
  userText,
296
321
  abortCtrl,
297
322
  acpSession: deps.acpSession,
298
323
  onText: (chunk, accumulated) => {
324
+ logFirstToken(chunk);
299
325
  turnStreamRuntime.onText(chunk, accumulated);
300
326
  },
301
327
  onToolCall: (call) => {
@@ -7,16 +7,26 @@ import {
7
7
  } from '../skills/router.js';
8
8
  import type { SkillIndexEntry } from '../skills/index.js';
9
9
  import type { SkillTier } from '../skills/runner.js';
10
- import type { LLMMessage } from '../llm.js';
10
+ import * as llm from '../llm.js';
11
+ import { resolveRoleLlm } from '../user-config.js';
12
+ import { debug } from '../debug/log.js';
11
13
 
12
14
  export interface DashboardSkillDetectionRuntimeDeps {
13
15
  llmFallback: boolean;
14
16
  keywordScoreThreshold: number;
15
17
  llmConfidenceThreshold: number;
16
18
  streamLLM: (
17
- messages: LLMMessage[],
19
+ messages: llm.LLMMessage[],
18
20
  onDelta: (chunk: string, full: string) => void,
19
- opts: { signal?: AbortSignal; maxTokens?: number; temperature?: number },
21
+ opts: {
22
+ signal?: AbortSignal;
23
+ maxTokens?: number;
24
+ temperature?: number;
25
+ /** classify role pin — resolveRoleLlm('classify')가 고른 모델·프로바이더. */
26
+ model?: string;
27
+ provider?: (typeof llm.PROVIDERS)[string];
28
+ usageRole?: 'classify';
29
+ },
20
30
  ) => Promise<string>;
21
31
  /** Active model tier — enables tier-aware full-menu LLM routing in
22
32
  * detectLLM (T1 frontier models route the whole menu when keyword is
@@ -69,10 +79,20 @@ export async function detectDashboardSkillRoute(
69
79
  if (!deps.llmFallback) return keywordDetection;
70
80
 
71
81
  const classify = async (prompt: string, signal?: AbortSignal): Promise<LLMClassifyResult> => {
82
+ const resolved = resolveRoleLlm('classify');
83
+ const provider = llm.PROVIDERS[resolved.provider];
84
+ if (!provider) throw new Error(`Unknown classify provider: ${resolved.provider}`);
85
+ debug.log('llm.role-pin', 'resolved', {
86
+ site: 'skill-detection-runtime',
87
+ role: 'classify',
88
+ model: resolved.model,
89
+ provider: resolved.provider,
90
+ source: resolved.source,
91
+ });
72
92
  const raw = await deps.streamLLM(
73
93
  [{ role: 'user', content: prompt }],
74
94
  () => {},
75
- { signal, maxTokens: 120, temperature: 0 },
95
+ { signal, maxTokens: 120, temperature: 0, model: resolved.model, provider, usageRole: 'classify' },
76
96
  );
77
97
  return parseClassifier(raw);
78
98
  };
@@ -2490,6 +2490,12 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps, decideLedg
2490
2490
  });
2491
2491
  if (result.kind === 'launch') {
2492
2492
  ctx.chatLines.push(ctx.accent(` ✅ 전제 검사 통과 — 골: ${result.goalFile}`));
2493
+ const { resolveObserveOnlyDecision } = await import('../../self-implement/observe-only.js');
2494
+ if (resolveObserveOnlyDecision().enabled) {
2495
+ ctx.chatLines.push(ctx.muted(' 관측 전용이라 런을 발사하지 않았습니다 · 코드도 안 바꿨습니다'));
2496
+ ctx.chatLines.push(ctx.muted(' 조회: elanous logs --category self-implement --since 10m'));
2497
+ return;
2498
+ }
2493
2499
  // ⭐ 런을 «띄운다» — detached 라 이 TUI 를 닫아도 런은 산다.
2494
2500
  // ⛔ spawn 을 여기서 새로 쓰지 않는다 — `launchDevGoalFileDetached` «한 자리»가 인자·env 를 갖는다.
2495
2501
  // ⛔ 그리고 「띄웠다」와 「끝났다」는 다른 값이다 — 이 표면은 «띄운 것»까지만 말한다.
package/src/debug/log.ts CHANGED
@@ -949,7 +949,7 @@ const SECRET_TEXT_RULES: readonly { readonly id: string; readonly re: RegExp; re
949
949
  { id: 'gcp-api-key', re: /\b(AIza)[0-9A-Za-z_-]{35}\b/g, to: '$1***' },
950
950
  { id: 'jwt', re: /\b(ey)[A-Za-z0-9_-]{10,}\.ey[A-Za-z0-9._\\/-]{10,}/g, to: '$1***' },
951
951
  // gitleaks: generic-api-key 축소판 — `key = value` 형태.
952
- { id: 'generic-api-key', re: /\b(api[_-]?key|secret|password|passwd|token|credential)\b(\s*[:=]\s*)["']?[^\s"',;)]{4,}/gi, to: '$1$2***' },
952
+ { id: 'generic-api-key', re: /\b(access[_-]?token|refresh[_-]?token|api[_-]?key|secret|password|passwd|token|credential)\b(\s*[:=]\s*)["']?[^\s"',;)]{4,}/gi, to: '$1$2***' },
953
953
  // 환경변수 형태 `OPENROUTER_API_KEY=…` — 위 규칙은 `_API_KEY` 앞에 단어 경계가 없어 못 잡는다. 값에 글자가 있어야 한다(`MAX_TOKEN=4096` 은 그대로).
954
954
  { id: 'env-secret', re: /\b([A-Z][A-Z0-9]*_(?:[A-Z0-9]+_)*(?:API_KEY|TOKEN|SECRET|PASSWORD|ACCESS_KEY))(\s*[:=]\s*)["']?(?=[^\s"',;)]*[A-Za-z])[^\s"',;)]{8,}/g, to: '$1$2***' },
955
955
  // BEDROCK-PROVIDER — `AWS_BEARER_TOKEN_BEDROCK=…` 은 이름이 TOKEN 으로 «끝나지» 않아 위 규칙이 못 잡는다.