elanous 0.2.4 → 0.2.6

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 (455) hide show
  1. package/README.md +7 -6
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
  9. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
  19. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
  93. package/apps/pwa/out/approvals/index.html +1 -1
  94. package/apps/pwa/out/approvals/index.txt +29 -27
  95. package/apps/pwa/out/autopilot/index.html +1 -1
  96. package/apps/pwa/out/autopilot/index.txt +29 -27
  97. package/apps/pwa/out/board/index.html +1 -1
  98. package/apps/pwa/out/board/index.txt +29 -27
  99. package/apps/pwa/out/botlab/index.html +1 -1
  100. package/apps/pwa/out/botlab/index.txt +26 -24
  101. package/apps/pwa/out/bots/index.html +1 -1
  102. package/apps/pwa/out/bots/index.txt +26 -24
  103. package/apps/pwa/out/chat/index.html +1 -1
  104. package/apps/pwa/out/chat/index.txt +26 -24
  105. package/apps/pwa/out/control/index.html +1 -1
  106. package/apps/pwa/out/control/index.txt +29 -27
  107. package/apps/pwa/out/design-check/index.html +1 -1
  108. package/apps/pwa/out/design-check/index.txt +29 -27
  109. package/apps/pwa/out/editor/index.html +1 -1
  110. package/apps/pwa/out/editor/index.txt +29 -27
  111. package/apps/pwa/out/healthz +1 -1
  112. package/apps/pwa/out/icons/elanous-16.png +0 -0
  113. package/apps/pwa/out/icons/elanous-180.png +0 -0
  114. package/apps/pwa/out/icons/elanous-192.png +0 -0
  115. package/apps/pwa/out/icons/elanous-32.png +0 -0
  116. package/apps/pwa/out/icons/elanous-512.png +0 -0
  117. package/apps/pwa/out/index.html +1 -1
  118. package/apps/pwa/out/index.txt +26 -24
  119. package/apps/pwa/out/intake/index.html +1 -1
  120. package/apps/pwa/out/intake/index.txt +29 -27
  121. package/apps/pwa/out/live/index.html +1 -1
  122. package/apps/pwa/out/live/index.txt +29 -27
  123. package/apps/pwa/out/manifest.webmanifest +5 -2
  124. package/apps/pwa/out/market/index.html +1 -1
  125. package/apps/pwa/out/market/index.txt +26 -24
  126. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  127. package/apps/pwa/out/missions/placeholder/index.txt +26 -24
  128. package/apps/pwa/out/morning/index.html +1 -1
  129. package/apps/pwa/out/morning/index.txt +29 -27
  130. package/apps/pwa/out/observatory/index.html +1 -1
  131. package/apps/pwa/out/observatory/index.txt +29 -27
  132. package/apps/pwa/out/reflection/index.html +1 -1
  133. package/apps/pwa/out/reflection/index.txt +27 -25
  134. package/apps/pwa/out/scheduler/index.html +1 -1
  135. package/apps/pwa/out/scheduler/index.txt +29 -27
  136. package/apps/pwa/out/sessions/index.html +1 -1
  137. package/apps/pwa/out/sessions/index.txt +26 -24
  138. package/apps/pwa/out/settings/devices/index.html +1 -1
  139. package/apps/pwa/out/settings/devices/index.txt +29 -27
  140. package/apps/pwa/out/settings/index.html +1 -1
  141. package/apps/pwa/out/settings/index.txt +29 -27
  142. package/apps/pwa/out/setup/done/index.html +1 -1
  143. package/apps/pwa/out/setup/done/index.txt +29 -27
  144. package/apps/pwa/out/setup/index.html +1 -1
  145. package/apps/pwa/out/setup/index.txt +29 -27
  146. package/apps/pwa/out/share/index.html +1 -1
  147. package/apps/pwa/out/share/index.txt +29 -27
  148. package/apps/pwa/out/showroom/index.html +1 -1
  149. package/apps/pwa/out/showroom/index.txt +26 -24
  150. package/apps/pwa/out/sw-strategies.js +44 -0
  151. package/apps/pwa/out/sw.js +6 -44
  152. package/apps/pwa/out/tasks/index.html +1 -1
  153. package/apps/pwa/out/tasks/index.txt +29 -27
  154. package/apps/pwa/out/term/index.html +1 -1
  155. package/apps/pwa/out/term/index.txt +29 -27
  156. package/apps/pwa/out/trace/index.html +1 -1
  157. package/apps/pwa/out/trace/index.txt +29 -27
  158. package/apps/pwa/out/vault/index.html +1 -1
  159. package/apps/pwa/out/vault/index.txt +29 -27
  160. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  161. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
  162. package/apps/pwa/out/workflows/index.html +1 -1
  163. package/apps/pwa/out/workflows/index.txt +29 -27
  164. package/apps/pwa/out/workspace/index.html +1 -1
  165. package/apps/pwa/out/workspace/index.txt +29 -27
  166. package/apps/pwa/out/worktrees/index.html +1 -1
  167. package/apps/pwa/out/worktrees/index.txt +29 -27
  168. package/catalog/resources.yaml +6 -6
  169. package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
  170. package/graphs/landing/landing-heal.yaml +1 -1
  171. package/graphs/release/recipes.yaml +11 -1
  172. package/graphs/release/release-loop.yaml +9 -2
  173. package/graphs/video/README.md +1 -0
  174. package/graphs/video/explainer-line.yaml +58 -0
  175. package/package.json +3 -1
  176. package/plugins/card-followup/README.md +33 -0
  177. package/plugins/card-followup/examples/card-sample.json +7 -0
  178. package/plugins/card-followup/graphs/card-followup.yaml +16 -0
  179. package/plugins/card-followup/graphs/recipes.yaml +12 -0
  180. package/plugins/card-followup/graphs/run-step.ts +172 -0
  181. package/plugins/card-followup/plugin.json +25 -0
  182. package/plugins/geo-check/README.md +13 -0
  183. package/plugins/geo-check/examples/brand-sample.json +8 -0
  184. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  185. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  186. package/plugins/geo-check/graphs/run-step.ts +177 -0
  187. package/plugins/geo-check/plugin.json +24 -0
  188. package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
  189. package/plugins/job-coach/README.md +42 -0
  190. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  191. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  192. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  193. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  194. package/plugins/job-coach/graphs/run-step.ts +211 -17
  195. package/plugins/job-coach/plugin.json +16 -3
  196. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  197. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  198. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  199. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  200. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  201. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  202. package/scripts/backup/elanous-backup.sh +4 -1
  203. package/scripts/build-docs-site.ts +4 -3
  204. package/scripts/coord-channel-watch.sh +23 -4
  205. package/scripts/coord-post.sh +49 -12
  206. package/scripts/coord-tracks.json +5 -5
  207. package/scripts/daemon-port-baseline.txt +1 -2
  208. package/scripts/eval-bot-vs-grok.ts +79 -0
  209. package/scripts/eval-grok-live-baseline.ts +36 -0
  210. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  211. package/scripts/fleet/node-setup.sh +46 -7
  212. package/scripts/landing-heal/must-fix-match.ts +26 -0
  213. package/scripts/landing-heal/run-mission.ts +114 -20
  214. package/scripts/lib/android-test-device.ts +32 -0
  215. package/scripts/lib/front-route-measurement.ts +131 -0
  216. package/scripts/lib/tui-sim-keys.ts +42 -0
  217. package/scripts/measure-front-route-classifier.ts +56 -0
  218. package/scripts/media/ocr-leak-scan.sh +21 -0
  219. package/scripts/public-export.ts +2 -3
  220. package/scripts/public-import-pr.ts +80 -6
  221. package/scripts/publish-internal-dist.ts +2 -3
  222. package/scripts/red-sweep-diff.ts +1 -1
  223. package/scripts/release-loop/auto-approve-node.ts +72 -0
  224. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  225. package/scripts/release-loop/gate-diff.ts +29 -0
  226. package/scripts/release-loop/gate-node.ts +366 -45
  227. package/scripts/release-loop/publish-node.ts +7 -3
  228. package/scripts/release-loop/release-docs.ts +20 -13
  229. package/scripts/release-loop/shard-plan.ts +47 -0
  230. package/scripts/release-loop/test-durations.ts +63 -0
  231. package/scripts/release-loop/upgrade-node.ts +3 -2
  232. package/scripts/test-audit-coverage-per-file.sh +16 -0
  233. package/scripts/test-audit-meaningless.ts +88 -0
  234. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  235. package/scripts/test-deterministic.ts +27 -5
  236. package/scripts/tui-sim.ts +59 -14
  237. package/scripts/ux/bump-installed.sh +20 -0
  238. package/scripts/ux/lib/cdp.ts +72 -0
  239. package/scripts/ux/record-page.ts +53 -0
  240. package/skills/explainer-video/SKILL.md +117 -0
  241. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  242. package/skills/explainer-video/engine/build.mjs +431 -0
  243. package/skills/explainer-video/engine/reel.mjs +128 -0
  244. package/skills/explainer-video/engine/reel.sh +8 -0
  245. package/skills/explainer-video/engine/vo.ts +79 -0
  246. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  247. package/src/agent/nested-agent-env.ts +1 -0
  248. package/src/agent-mission/driver.ts +95 -16
  249. package/src/agent-mission/mission-cli.ts +6 -0
  250. package/src/agent-mission/resource-ladder.ts +73 -25
  251. package/src/auth/acp-token.ts +16 -0
  252. package/src/auth/github-app-token.ts +112 -0
  253. package/src/autopilot/gh-rest-fallback.ts +177 -0
  254. package/src/autopilot/mission-origin.ts +20 -4
  255. package/src/autopilot/mission-phase-track.ts +11 -1
  256. package/src/autopilot/pr-manager.ts +51 -4
  257. package/src/autopilot/track-agent.ts +3 -3
  258. package/src/autopilot/track-courier.ts +2 -2
  259. package/src/boot/acp-server.ts +2 -16
  260. package/src/boot/daemon-tools/web-search.ts +2 -1
  261. package/src/boot/ws-bridge.ts +11 -0
  262. package/src/chat/index.ts +37 -8
  263. package/src/chat/log-scroll-key.ts +12 -0
  264. package/src/chat/submit-trace.ts +30 -0
  265. package/src/cli/agent-cli.ts +7 -2
  266. package/src/cli/decisions-cli.ts +94 -0
  267. package/src/cli/doctor-cli.ts +40 -54
  268. package/src/cli/doctor-readiness.ts +16 -24
  269. package/src/cli/intake-cli.ts +1 -1
  270. package/src/cli/mcp-call.ts +9 -0
  271. package/src/cli/mcp-gateway-cli.ts +7 -3
  272. package/src/cli/nexus-ios-bind.ts +25 -8
  273. package/src/cli/phone-link.ts +117 -0
  274. package/src/cli/plugin-cli.ts +136 -11
  275. package/src/cli/pr-lineage.ts +2 -1
  276. package/src/cli/private-ledger-commands.ts +31 -0
  277. package/src/cli/pwa-start.ts +3 -0
  278. package/src/cli/release-cli.ts +97 -7
  279. package/src/cli/self-update.ts +7 -4
  280. package/src/cli/start-cli.ts +33 -14
  281. package/src/dashboard/acp-boot.ts +3 -0
  282. package/src/dashboard/index.ts +62 -13
  283. package/src/dashboard/input/no-provider-reply.ts +13 -0
  284. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  285. package/src/dashboard/stale-install-check.ts +77 -0
  286. package/src/decisions/decision-ledger.ts +404 -0
  287. package/src/domains/outbound-alert.ts +13 -7
  288. package/src/domains/telegram-kind-route.ts +21 -0
  289. package/src/execution-loop/dispatch-task.ts +139 -0
  290. package/src/execution-loop/gate-decision-summary.ts +29 -0
  291. package/src/execution-loop/gates.ts +197 -0
  292. package/src/field/field-media.ts +245 -0
  293. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  294. package/src/graph-kinds/plugin-nodes.ts +10 -2
  295. package/src/graph-runner/runner.ts +35 -5
  296. package/src/grounding/token.ts +12 -6
  297. package/src/harness/harness-cli-command.ts +68 -9
  298. package/src/harness/harness-pod-dispatch.ts +8 -2
  299. package/src/harness/harness-stop.ts +147 -18
  300. package/src/harness/self-send-decide.ts +26 -17
  301. package/src/harness/self-send-target.ts +22 -1
  302. package/src/harness/worktree-branch-prefix.ts +4 -3
  303. package/src/index.ts +50 -13
  304. package/src/instance/resolve.ts +6 -0
  305. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  306. package/src/intake-plane/digest.ts +11 -2
  307. package/src/live/detail-switch.ts +8 -3
  308. package/src/llm/usable-llm.ts +32 -0
  309. package/src/loops/registry.ts +27 -5
  310. package/src/market/publish.ts +59 -3
  311. package/src/native-tool-catalog.ts +2 -2
  312. package/src/nexus/api/field-uploads.ts +143 -0
  313. package/src/nexus/api/graph-approvals.ts +174 -0
  314. package/src/nexus/api/harness-api.ts +219 -5
  315. package/src/nexus/api/http-server.ts +69 -6
  316. package/src/nexus/api/log-fabric.ts +10 -1
  317. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  318. package/src/nexus/api/merge-approvals.ts +2 -1
  319. package/src/nexus/api/meta-api.ts +14 -0
  320. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  321. package/src/nexus/api/plugins-market.ts +87 -3
  322. package/src/nexus/api/pod-credential-api.ts +68 -14
  323. package/src/nexus/api/public-routes.ts +7 -0
  324. package/src/nexus/api/trace.ts +57 -6
  325. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  326. package/src/nexus/index.ts +33 -32
  327. package/src/nexus/outbound/router.ts +1 -1
  328. package/src/notify/llm-budget-alert.ts +119 -0
  329. package/src/onboarding.ts +25 -2
  330. package/src/plugins/core/host.ts +14 -0
  331. package/src/plugins/install/market-fetch.ts +4 -3
  332. package/src/plugins/install/plugin-credentials.ts +106 -0
  333. package/src/plugins/install/plugin-install.ts +163 -32
  334. package/src/plugins/maker/plugin-maker.ts +208 -0
  335. package/src/prompt-library/universal-preamble.ts +8 -2
  336. package/src/relay/channel.ts +184 -0
  337. package/src/relay/mux.ts +182 -0
  338. package/src/relay/test-vectors.json +29 -0
  339. package/src/release-loop/checklist.ts +217 -0
  340. package/src/release-loop/release-note.ts +6 -3
  341. package/src/seat-address/seat-address.ts +34 -0
  342. package/src/self-dev/dev-cli.ts +40 -20
  343. package/src/self-dev/dev-pipeline.ts +77 -1
  344. package/src/self-implement/gate-baseline.ts +3 -1
  345. package/src/self-implement/goal-author.ts +18 -1
  346. package/src/self-implement/host-regate.ts +6 -2
  347. package/src/self-implement/orchestrator.ts +7 -2
  348. package/src/self-implement/run-ledger-compact.ts +1 -1
  349. package/src/self-implement/seams.ts +4 -2
  350. package/src/session/index.ts +23 -10
  351. package/src/skills/tools/dev-harness.ts +42 -0
  352. package/src/steward/steward-cards.ts +45 -0
  353. package/src/steward/triage.ts +50 -14
  354. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  355. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  356. package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  358. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  359. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  360. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  361. package/src/telegram-report.ts +12 -6
  362. package/src/telegram.ts +108 -1
  363. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  364. package/src/tui.ts +3 -2
  365. package/src/user-config.ts +32 -2
  366. package/src/util/spawn-sync-output.ts +15 -0
  367. package/src/version/code-revision.ts +1 -1
  368. package/src/version/packed-revision.json +1 -1
  369. package/src/video-pipeline/recipes/explainer.ts +213 -0
  370. package/src/video-pipeline/walk-line.ts +3 -2
  371. package/src/web-search/free.ts +77 -0
  372. package/src/web-search/index.ts +2 -0
  373. package/src/workflow-runtime/deps-bridge.ts +4 -3
  374. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  375. package/src/workflow-runtime/types.ts +2 -1
  376. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  388. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  454. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  455. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
package/src/chat/index.ts CHANGED
@@ -107,9 +107,27 @@ import {
107
107
  import { resolveTextInputControlAction } from './input-control-key.js';
108
108
  import { resolveTextInputEditAction } from './input-edit-key.js';
109
109
  import { resolveTextInputTextAction } from './input-text-key.js';
110
+ import { classifyLeftoverInput, summarizeSubmitText } from './submit-trace.js';
110
111
 
111
112
  import { perf } from '../perf-counters.js';
112
113
 
114
+ let lastSubmitAt: number | null = null;
115
+
116
+ function traceAfterSubmitKey(key: Key): void {
117
+ if (lastSubmitAt === null) return;
118
+ const withinMs = Date.now() - lastSubmitAt;
119
+ if (withinMs > 1000) {
120
+ lastSubmitAt = null;
121
+ return;
122
+ }
123
+ if (key.ctrl || key.alt) return;
124
+ const input = key.name === 'paste' ? key.paste ?? '' : key.name;
125
+ if (!input || (key.name !== 'paste' && [...input].length !== 1)) return;
126
+ lastSubmitAt = null;
127
+ const kind = classifyLeftoverInput(input, withinMs);
128
+ if (kind) debug.log('chat.input.leftover', 'after-submit', { withinMs, kind });
129
+ }
130
+
113
131
  /** Row where the input prompt starts — set by the textInput paint
114
132
  * loop so writeTerminal knows which rows to invalidate in the
115
133
  * frame cache. Falls back to "invalidate everything" when unknown
@@ -419,14 +437,14 @@ export const SLASH_COMMANDS: SlashCommand[] = [
419
437
  // B4 — craft rulebook verdict, same resolution as `elanous repo design-check`
420
438
  // and the PWA `/design-check` panel. Listed (not baselined as hidden) on
421
439
  // purpose: a surface nobody can discover is not a surface.
422
- { name: 'design', aliases: ['design-check'], description: 'Craft rulebooks — 이 저장소 DESIGN.md 가 선언한 규칙집 · 못 찾은 것 · elanous 가 주는데 선언 안 된 것. /design [--declared] · /design pick [번호|id] 디자인 방향 선택', subcommands: ['--declared', 'pick'] },
440
+ { name: 'design', aliases: ['design-check'], description: 'Design rulebooks this repository declares · /design [--declared] · /design pick [number|id] to choose a design direction', subcommands: ['--declared', 'pick'] },
423
441
  { name: 'provider', aliases: ['p'], description: 'LLM providers — /provider (list) · next (cycle, also Alt+M / pill click) · use <name> · pick (visual picker) · reset', subcommands: ['next', 'use', 'pick', 'picker', 'menu', 'reset', 'list'] },
424
442
  { name: 'reasoning', aliases: ['r', 'think'], description: 'Reasoning level (codex effort + summary, anthropic extended-thinking budget) — /reasoning [off|low|medium|high|xhigh] (cycle when no arg · xhigh 는 모델 상한이 xhigh 이상일 때만 wire 에 실린다, 아니면 high 로 깎인다)', subcommands: ['off', 'low', 'medium', 'high', 'xhigh'] },
425
443
  { name: 'model', aliases: ['m'], description: 'Switch active model — /model <codex|terra|sol|luna|opus|sonnet|grok> (list + current when no arg). OpenAI 는 Codex(Responses API)만. effort 는 /reasoning.', subcommands: ['codex', 'terra', 'sol', 'luna', 'opus', 'sonnet', 'grok'] },
426
444
  { name: 'local', aliases: ['ll'], description: 'Local OpenAI-compatible LLM — ping / models / test / use', subcommands: ['ping', 'models', 'test', 'use', 'status'] },
427
445
  { name: 'session', aliases: ['sess'], description: 'Session resume — list / load / sync / new (handoff from mobile)', subcommands: ['list', 'load', 'sync', 'new'] },
428
- { name: 'resume', aliases: [], description: 'Resume session — 픽커에서 선택 · /resume <id-prefix> 즉시 로드 (TUI 부활 S-a)' },
429
- { name: 'fork', aliases: [], description: 'Fork — 현 시점 전체 복사로 새 세션 분기 (codex /fork 동형)' },
446
+ { name: 'resume', aliases: [], description: 'Pick a past session, or load one by id prefix · /resume <id-prefix>' },
447
+ { name: 'fork', aliases: [], description: 'Copy this session into a new one' },
430
448
  { name: 'rewind', aliases: [], description: 'Rewind — 과거 user 턴 픽커로 되감기 (원본 보존 · 새 세션 분기 · /rewind <n> 숏컷)' },
431
449
  // TUI 부활 C-a (2026-07-12): autopilot 미션 TUI 표면 — CLI 동등 단일 창구 소비.
432
450
  { name: 'mission', aliases: [], description: 'Autopilot 미션 — list(+헬스) · trace <id> 계보 · arm <id> 승인 (구체화/종료는 CLI)', subcommands: ['list', 'trace', 'arm'] },
@@ -468,7 +486,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
468
486
  { name: 'acp', aliases: [], description: 'ACP chat — stream claude-code / codex / gemini replies into the chat pane (not a VW spawn). /acp codex points at the canonical codex app-server path; /acp cas is a synonym.', subcommands: ['claude', 'codex', 'gemini', 'cas', 'cancel', 'status', 'drop'] },
469
487
  { name: 'conv', aliases: [], description: 'Conversation widget/popup host — /conv list · /conv open <session-id> · /conv layout <cascade|tile|stack> · /conv focus <next|prev>.', subcommands: ['list', 'ls', 'open', 'layout', 'focus'] },
470
488
  { name: 'handoff', aliases: [], description: 'H5 P3 cross-agent context handoff: /handoff <from_session_id> <to_brand> [--channels r,p,m] [--prompt "prefix"]. Takes source session snapshot (filtered by channels if observer present) and launches target via adapter registry. Brands: codex · claude · claude-code · gemini · elanous.' },
471
- { name: 'remaining', aliases: [], description: '계정마다 행으로 「지금 쓸 수 있는 것이 얼마나 남았나」를 본다 — 크레딧 축과 구독 축을 갈라 낸다. 화면은 명령 산출을 그대로 읽는다.' },
489
+ { name: 'remaining', aliases: [], description: 'What each account has left' },
472
490
  { name: 'agent-room', aliases: [], description: 'H6 P4 VW agent-room — /agent-room <N> <brands...> · /agent-room list · /agent-room close <id> · N ∈ {2,3,4} · brands: codex/claude/gemini/elanous/auto/lll:<m>', subcommands: ['list', 'close', 'preset', 'help'] },
473
491
  { name: 'showroom', aliases: ['sr'], description: 'Showroom v2 multi-LLM lane composer — /showroom (default 2-pane) · /showroom <lane1> <lane2> [<lane3> [<lane4>]] · lane = role:provider[:transport] · roles plan/build/exec/review/reflect · pair with /lane and /relay for cross-lane handoffs.', subcommands: ['help'] },
474
492
  { name: 'reply', aliases: [], description: 'H6 P5 AgentReply — /reply <target-session-id> <message...> · send message to a live session, capture the response · flags: --from · --channels r,m · --idle-ms · --timeout-ms', subcommands: ['help'] },
@@ -701,6 +719,8 @@ export async function textInput(opts: {
701
719
  * typing. The host is expected to mutate its own state; the input
702
720
  * redraws the bar + picker on top afterward. */
703
721
  onMouse?: (mouse: { row: number; col: number; type: 'click' | 'double-click' | 'right-click' | 'scroll-up' | 'scroll-down' | 'drag' | 'release'; shift?: boolean }) => void | boolean | Promise<void | boolean>;
722
+ /** Scroll the host's chat log from the input without editing its buffer. */
723
+ onScrollKey?: (name: 'pageup' | 'pagedown') => boolean | void;
704
724
  /** MX11b — key interceptor called BEFORE textInput's own key
705
725
  * handling. Host returns 'consumed' to short-circuit the event
706
726
  * (no further input processing), 'passthrough' to let the default
@@ -1331,13 +1351,15 @@ export async function textInput(opts: {
1331
1351
  writeTerminal(endTextInputCursor(opts.cursorSink));
1332
1352
  return result;
1333
1353
  };
1334
- const submitBufferResult = (text: string): InputResult => (
1335
- finalizeInput({
1354
+ const submitBufferResult = (text: string): InputResult => {
1355
+ debug.log('chat.input.submit', 'summary', summarizeSubmitText(text.trim()));
1356
+ lastSubmitAt = Date.now();
1357
+ return finalizeInput({
1336
1358
  text: text.trim(),
1337
1359
  submitted: true,
1338
1360
  ...(pendingExternalSubmitSource ? { externalSubmitSource: pendingExternalSubmitSource } : {}),
1339
- })
1340
- );
1361
+ });
1362
+ };
1341
1363
  const currentBufferText = (): string => lines.join('\n').trim();
1342
1364
 
1343
1365
  // Expose drawAll so the host can ping us to re-paint after an
@@ -1401,6 +1423,7 @@ export async function textInput(opts: {
1401
1423
  }
1402
1424
 
1403
1425
  const key = await (opts.readKey ?? (() => readKey('input')))();
1426
+ traceAfterSubmitKey(key);
1404
1427
 
1405
1428
  // Raw-key trace — diagnoses "modifier bit dropped before global-action
1406
1429
  // matcher saw the chord" cases (e.g. tablet terminal apps that
@@ -1627,6 +1650,11 @@ export async function textInput(opts: {
1627
1650
  }
1628
1651
  }
1629
1652
 
1653
+ if (!key.ctrl && (key.name === 'pageup' || key.name === 'pagedown') && opts.onScrollKey) {
1654
+ if (opts.onScrollKey(key.name) !== false) drawAll();
1655
+ continue;
1656
+ }
1657
+
1630
1658
  const controlAction = resolveTextInputControlAction(key, {
1631
1659
  currentLine: lines[lineIdx] ?? '',
1632
1660
  cursor: colIdx,
@@ -1897,6 +1925,7 @@ export function attachStreamingKeys(
1897
1925
  };
1898
1926
  function deliver(keys: readonly Key[]): void {
1899
1927
  for (const key of keys) {
1928
+ traceAfterSubmitKey(key);
1900
1929
  traceKey(key, 'stream');
1901
1930
  try {
1902
1931
  const r = handler(key);
@@ -0,0 +1,12 @@
1
+ export function nextChatScrollOffset(
2
+ current: number,
3
+ key: 'pageup' | 'pagedown',
4
+ pageSize: number,
5
+ maxOffset: number,
6
+ ): number {
7
+ if (maxOffset <= 0) return -1;
8
+ const offset = current < 0 ? maxOffset : Math.max(0, Math.min(current, maxOffset));
9
+ if (key === 'pageup') return Math.max(0, offset - pageSize);
10
+ const next = offset + pageSize;
11
+ return next >= maxOffset ? -1 : next;
12
+ }
@@ -0,0 +1,30 @@
1
+ const LONE_JAMO = /[\u3131-\u318e\u1100-\u11ff]/u;
2
+ const HANGUL_SYLLABLE = /[\uac00-\ud7a3]/u;
3
+
4
+ export function summarizeSubmitText(text: string): {
5
+ chars: number;
6
+ lines: number;
7
+ lastIsLoneJamo: boolean;
8
+ loneJamoCount: number;
9
+ hasHangul: boolean;
10
+ } {
11
+ const characters = [...text];
12
+ const loneJamoCount = characters.filter(char => LONE_JAMO.test(char)).length;
13
+ return {
14
+ chars: characters.length,
15
+ lines: text.split('\n').length,
16
+ lastIsLoneJamo: LONE_JAMO.test(characters.at(-1) ?? ''),
17
+ loneJamoCount,
18
+ hasHangul: loneJamoCount > 0 || HANGUL_SYLLABLE.test(text),
19
+ };
20
+ }
21
+
22
+ export function classifyLeftoverInput(
23
+ input: string,
24
+ msSinceSubmit: number,
25
+ ): 'syllable' | 'jamo' | null {
26
+ if (msSinceSubmit < 0 || msSinceSubmit > 1000 || [...input].length !== 1) return null;
27
+ if (HANGUL_SYLLABLE.test(input)) return 'syllable';
28
+ if (LONE_JAMO.test(input)) return 'jamo';
29
+ return null;
30
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { resolve } from 'node:path';
3
3
  import { getUserConfig, reloadUserConfig } from '../user-config.js';
4
- import { needsOnboarding, runOnboarding } from '../onboarding.js';
4
+ import { handleOnboardingRefusal, needsOnboarding, runOnboarding } from '../onboarding.js';
5
5
  import { runTurn, ensureCliSession, sessionBudget } from '../session/chat.js';
6
6
  import { NoLlmProviderAvailableError, noProviderAvailableMessage } from '../llm.js';
7
7
  import { resolveSessionId, getActiveSessionId, setActiveSessionId } from '../session/index.js';
@@ -93,7 +93,12 @@ export function registerAgentCommands(program: Command): void {
93
93
  const cfg = getUserConfig();
94
94
  if (needsOnboarding(cfg)) {
95
95
  ui.info('No config yet — launching setup wizard first.');
96
- await runOnboarding();
96
+ try {
97
+ await runOnboarding();
98
+ } catch (error) {
99
+ if (handleOnboardingRefusal(error, 'agent')) return;
100
+ throw error;
101
+ }
97
102
  }
98
103
  const refreshed = reloadUserConfig();
99
104
  await runChatTurnCli({
@@ -0,0 +1,94 @@
1
+ import type { Command } from 'commander';
2
+ import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
3
+
4
+ const repeat = (value: string, values: string[]) => [...values, value];
5
+ function date(raw?: string): string | undefined {
6
+ if (!raw) return undefined;
7
+ const days = /^(\d+)d$/.exec(raw);
8
+ const parsed = days ? new Date(Date.now() - Number(days[1]) * 86400000) : new Date(raw);
9
+ if (!Number.isFinite(parsed.getTime())) throw new Error(`invalid since: ${raw}`);
10
+ return parsed.toISOString();
11
+ }
12
+ const kst = (at: string) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium' }).format(new Date(at)) + ' KST';
13
+ const version = (e: DecisionEntry['version']) => e ? `released=${e.released ?? '-'} · dev=${e.dev ?? '-'}${e.codename ? ` «${e.codename}»` : ''}` : '판 미상';
14
+ function agent(o?: string): string {
15
+ const value = o?.trim() || process.env.AI_AGENT?.trim();
16
+ if (!value) throw new Error('agent unknown: provide --agent or AI_AGENT');
17
+ return value;
18
+ }
19
+ function parseOption(raw: string): DecisionOption {
20
+ const m = /^([a-z])=(.+?):(.+)$/.exec(raw);
21
+ if (!m) throw new Error(`invalid --option: ${raw} (expected a=label:consequence)`);
22
+ return { key: m[1]!, label: m[2]!, consequence: m[3]! };
23
+ }
24
+ export function formatDecisionRow(e: DecisionEntry): string {
25
+ const recommendation = 'skipped' in e.recommendation ? '권고 생략' : `${e.recommendation.option} (${e.recommendation.why})`;
26
+ return `${e.id} · ${e.category} · ${e.title} · ${recommendation} · ${e.raisedAt ? kst(e.raisedAt) : '올린 시각 미상'} · ${version(e.version)}`;
27
+ }
28
+ export function formatDecisionDetail(e: DecisionEntry): string {
29
+ return [formatDecisionRow(e), `상태: ${e.status}`, `S: ${e.scqa.s}`, `C: ${e.scqa.c}`, `Q: ${e.scqa.q ?? '(비움)'}`, `A: ${e.scqa.a ?? '(비움)'}`,
30
+ '선택지 | 결과', ...(e.options.length ? e.options.map(o => `${o.key}: ${o.label} | ${o.consequence}`) : ['원문에 선택지 없음 · 확인 필요']),
31
+ 'skipped' in e.recommendation ? `권고 생략: ${e.recommendation.reason}` : `권고: ${e.recommendation.option} — ${e.recommendation.why}`,
32
+ `올림: ${e.raisedAt ? kst(e.raisedAt) : '시각 미상'} · ${e.raisedBy.agent} · ${version(e.version)}`,
33
+ ...(e.importedAt ? [`가져옴: ${kst(e.importedAt)}`] : []),
34
+ ...(e.status === 'decided' ? [`결정: ${e.decidedAt ? kst(e.decidedAt) : '시각 미상'} · ${e.decidedBy?.kind === 'auto' ? `AUTO ${e.decidedBy.agent} (${e.decidedBy.delegation})` : e.decidedBy?.kind === 'human' ? 'human' : '주체 미상'} · ${e.choice ?? '선택 키 미상'} · ${e.versionAtDecision ? version(e.versionAtDecision) : '결정 당시 판 미상'}`,
35
+ ...(e.note ? [`메모: ${e.note}`] : [])] : []),
36
+ ...(e.withdrawnAt ? [`철회: ${kst(e.withdrawnAt)} · ${e.withdrawReason}`] : []),
37
+ '이력:', ...e.history.map(h => ` ${h.type} · ${h.at ? kst(h.at) : '시각 미상'} · ${h.by}${h.choice ? ` · ${h.choice}` : ''}${h.reason ? ` · ${h.reason}` : ''} · ${h.version ? version(h.version) : '판 미상'}`),
38
+ ...(e.refs?.length ? [`참조: ${e.refs.join(' · ')}`] : [])].join('\n');
39
+ }
40
+
41
+ export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console): void {
42
+ const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
43
+ const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
44
+ const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
45
+ root.command('raise').description('SCQA·선택지와 권고로 결정 항목을 올린다')
46
+ .requiredOption('--title <text>').requiredOption('--category <category>')
47
+ .requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
48
+ .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
49
+ .option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
50
+ .option('--track <track>').option('--agent <agent>').option('--session <id>')
51
+ .option('--ref <url>', '참조 (반복)', repeat, [] as string[]).option('--json')
52
+ .action((o: { title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
53
+ if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
54
+ if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
55
+ const options: DecisionOption[] = o.option.map(parseOption);
56
+ const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
57
+ recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
58
+ raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}) });
59
+ emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
60
+ }));
61
+ root.command('list').description('결정 목록 (기본 열린 것)')
62
+ .option('--status <open|decided|all>', '기본 open', 'open').option('--since <date>').option('--version <version>')
63
+ .option('--decided-by <human|auto>').option('--category <category>').option('--json')
64
+ .action((o: { status: 'open' | 'decided' | 'all'; since?: string; version?: string; decidedBy?: 'human' | 'auto'; category?: DecisionCategory; json?: boolean }) => fail(() => {
65
+ const rows = new DecisionLedger(config).list({ status: o.status, since: date(o.since), version: o.version, decidedBy: o.decidedBy, category: o.category });
66
+ emit(rows, o.json, rows.length ? rows.map(formatDecisionRow).join('\n') : '결정 0건');
67
+ }));
68
+ root.command('show <id>').description('SCQA · 선택지 · 권고 · 이력 · 판').option('--json')
69
+ .action((id: string, o: { json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).show(id); emit(entry, o.json, formatDecisionDetail(entry)); }));
70
+ root.command('decide <id> <option>').description('사람 또는 AUTO 위임 결정 기록')
71
+ .option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json')
72
+ .action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean }) => fail(() => {
73
+ if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
74
+ if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
75
+ const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
76
+ const entry = new DecisionLedger(config).decide(id, choice, by, o.note);
77
+ emit(entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
78
+ }));
79
+ root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
80
+ .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
81
+ .option('--agent <agent>').option('--json')
82
+ .action((id: string, o: { option: string[]; agent?: string; json?: boolean }) => fail(() => {
83
+ const entry = new DecisionLedger(config).addOptions(id, o.option.map(parseOption), agent(o.agent));
84
+ emit(entry, o.json, formatDecisionDetail(entry));
85
+ }));
86
+ root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json')
87
+ .action((id: string, o: { reason: string; json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
88
+ root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json')
89
+ .action((file: string, o: { json?: boolean }) => fail(() => {
90
+ const result = importDecisionMarkdown(new DecisionLedger(config), file);
91
+ emit(result, o.json, `가져옴 ${result.imported.length} · 기존 ${result.existing.length} · 불완전 ${result.incomplete.length} · 못 읽음 ${result.unread.length}${result.incomplete.length ? `\n${result.incomplete.join('\n')}` : ''}${result.unread.length ? `\n${result.unread.join('\n')}` : ''}`);
92
+ if (result.unread.length) process.exitCode = 1;
93
+ }));
94
+ }
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  import type { Command } from 'commander';
9
9
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
10
10
  import { discoverChromeBinary } from '../browser-cdp/client.js';
11
- import { listProviders } from '../oauth/store.js';
11
+ import { resolveUsableLlm, type UsableLlm } from '../llm/usable-llm.js';
12
12
  import { detectLocale } from '../expression/locale.js';
13
13
  import { findRetiredConfigKeysInFile, getUserConfig, type RetiredConfigKey, type UserConfig } from '../user-config.js';
14
14
  import { codeRevision } from '../version/code-revision.js';
@@ -142,7 +142,8 @@ export interface DoctorOptions {
142
142
  */
143
143
  readiness?: ReadinessDeps;
144
144
  checkReadiness?: (deps: ReadinessDeps) => ReadinessReport;
145
- /** Read-only auth-store provider names. Values are never requested. */
145
+ resolveLlm?: () => UsableLlm;
146
+ /** Legacy read-only seam; route selection no longer uses provider-name presence. */
146
147
  listAuthProviders?: () => readonly string[];
147
148
  /** Read-only `codeRevision()`. */
148
149
  codeRevision?: () => string | undefined;
@@ -187,15 +188,6 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
187
188
  const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
188
189
  const tavilyNames = new Set(['TAVILY_API_KEY', 'TAVILY_KEY']);
189
190
 
190
- function codexLoginPresent(providers: readonly string[]): boolean {
191
- return providers.some((name) => name === 'openai-codex' || name.startsWith('openai-codex:'));
192
- }
193
-
194
- /** Presence only. Token strings from the auth store never leave this function. */
195
- function defaultListAuthProviders(): readonly string[] {
196
- return listProviders();
197
- }
198
-
199
191
  function defaultCodeRevision(): string | undefined {
200
192
  try {
201
193
  return codeRevision();
@@ -294,7 +286,6 @@ interface ReadinessLookup {
294
286
  platform: NodeJS.Platform;
295
287
  commandExists: (name: string) => boolean;
296
288
  getConfig: () => UserConfig;
297
- listAuthProviders: () => readonly string[];
298
289
  codeRevision: () => string | undefined;
299
290
  fetchHealth: () => { daemonSha?: string } | null;
300
291
  readInstallPrefix: () => string | null | undefined;
@@ -306,8 +297,6 @@ interface ReadinessLookup {
306
297
  /** 저장소가 시험한 bun 판(`.bun-version`) · 지금 도는 bun 판(시험 seam). */
307
298
  bunPin?: () => string | null;
308
299
  bunVersion?: () => string | null;
309
- /** LLM 키가 하나라도 풀렸나(자격 보고서에서 · 없으면 못 쟀다). */
310
- llmKeyResolved?: boolean;
311
300
  /** `/etc/os-release` 본문(시험 seam) — 못 읽으면 null. */
312
301
  readOsRelease?: () => string | null;
313
302
  /** 빌드 도구 탐침(시험 seam). */
@@ -371,19 +360,6 @@ function resolveReadinessDeps(lookup: ReadinessLookup): ReadinessDeps {
371
360
  if (provider !== null && provider !== 'auto' && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(provider)) {
372
361
  provider = null;
373
362
  }
374
- let codexLogin: boolean | null = null;
375
- let anyLogin: boolean | null = null;
376
- try {
377
- const providers = lookup.listAuthProviders();
378
- codexLogin = codexLoginPresent(providers);
379
- anyLogin = providers.length > 0;
380
- } catch {
381
- codexLogin = null;
382
- }
383
- // 로그인이 있거나 LLM 키가 풀렸으면 true · 둘 다 «아니라고 잰» 경우만 false · 하나라도 못 쟀으면 null.
384
- const llmCredentialAvailable = anyLogin === true || lookup.llmKeyResolved === true
385
- ? true
386
- : anyLogin === false && lookup.llmKeyResolved === false ? false : null;
387
363
  const pathEntries = (lookup.env.PATH ?? '').split(lookup.pathDelimiter).filter((entry) => entry.length > 0);
388
364
  let ghOnPath: boolean | null = null;
389
365
  let ghAuthStatus: number | null = null;
@@ -466,8 +442,6 @@ function resolveReadinessDeps(lookup: ReadinessLookup): ReadinessDeps {
466
442
  }
467
443
  return {
468
444
  provider,
469
- codexLogin,
470
- llmCredentialAvailable,
471
445
  ghOnPath,
472
446
  ghAuthStatus,
473
447
  ghVersion,
@@ -1045,8 +1019,7 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
1045
1019
  pathDelimiter,
1046
1020
  platform,
1047
1021
  commandExists,
1048
- getConfig,
1049
- listAuthProviders: options.listAuthProviders ?? defaultListAuthProviders,
1022
+ getConfig: () => resolutionOptions.userConfig,
1050
1023
  codeRevision: options.codeRevision ?? defaultCodeRevision,
1051
1024
  fetchHealth: options.fetchHealth ?? (() => defaultFetchHealth(options.resolveDaemonEndpoint ?? resolveDaemonEndpoint)),
1052
1025
  readInstallPrefix: options.readInstallPrefix ?? (() => defaultReadInstallPrefix(root, exists)),
@@ -1054,12 +1027,17 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
1054
1027
  ghAuthStatus: options.ghAuthStatus ?? (() => defaultGhAuthStatus(commandExists)),
1055
1028
  ghVersion: options.ghVersion ?? (() => defaultGhVersion(commandExists)),
1056
1029
  tmpdirSameFsAsBunCache: options.tmpdirSameFsAsBunCache ?? (() => defaultTmpdirSameFsAsBunCache(env)),
1057
- llmKeyResolved: llmKeyResolvedFrom(credentials),
1058
1030
  nodePty: nodePtyStatus(externalCommands.commands),
1059
1031
  ...(options.probeBuildToolchain ? { probeBuildToolchain: options.probeBuildToolchain } : {}),
1060
1032
  ...(options.checkPythonEnv ? { checkPythonEnv: options.checkPythonEnv } : {}),
1061
1033
  ...(options.probeHostEnvironment ? { probeHostEnvironment: options.probeHostEnvironment } : {}),
1062
1034
  });
1035
+ // An injected readiness snapshot stays as-is. For a real machine, measure the
1036
+ // same runtime route as `start` rather than inferring usability from key names.
1037
+ if (options.readiness === undefined && readiness.provider !== null) {
1038
+ try { readiness.usableLlm = options.resolveLlm ? options.resolveLlm() : resolveUsableLlm({ config: resolutionOptions.userConfig }); }
1039
+ catch { readiness.usableLlm = null; }
1040
+ }
1063
1041
  return {
1064
1042
  ok: true,
1065
1043
  credentials,
@@ -1083,15 +1061,14 @@ function formatReadiness(readiness: ReadinessReport | undefined): string[] {
1083
1061
  if (readiness === undefined) return [];
1084
1062
  const todo = readiness.items.filter((entry) => entry.status === 'manual' || entry.status === 'fixable');
1085
1063
  return [
1086
- '준비 상태:',
1064
+ 'Readiness:',
1087
1065
  ...readiness.items.map((entry) => {
1088
1066
  const remedy = entry.remedy === undefined ? '' : ` — ${entry.remedy}`;
1089
1067
  return ` ${entry.id}: ${entry.status} — ${entry.evidence}${remedy}`;
1090
1068
  }),
1091
1069
  todo.length === 0
1092
- ? '할 일 없음.'
1093
- // 처방은 줄마다 이미 있다 — 「fixable」이 곧 `doctor --fix` 는 아니다(install-path 의 처방은 export 한 줄).
1094
- : `할 일 ${todo.length}개: ${todo.map((entry) => entry.id).join(', ')} — 처방은 위 각 줄 끝`,
1070
+ ? 'Nothing to do.'
1071
+ : `To do (${todo.length}): ${todo.map((entry) => entry.id).join(', ')} — each line above ends with its fix`,
1095
1072
  ];
1096
1073
  }
1097
1074
 
@@ -1105,14 +1082,21 @@ function humanFreeFallback(value: string): string {
1105
1082
  .replace(/\s{2,}/g, ' ').trim().replace(/[—–·;,\s]+$/, '');
1106
1083
  }
1107
1084
 
1085
+ export function humanBreaks(value: string): string | undefined {
1086
+ if (value.trim().toLowerCase() === 'unmeasured'
1087
+ || /\b\d+ test (failures|errors)\b|\bmeasured\b|\bpostmortem\b|\bquota\b/i.test(value)
1088
+ || /[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(value)) return undefined;
1089
+ return humanFreeFallback(value) || undefined;
1090
+ }
1091
+
1108
1092
  function formatCapabilitySummary(summary: DoctorCapabilitySummary): string[] {
1109
1093
  const unavailable = summary.unavailable.map((capability) =>
1110
1094
  ` ${capability.name} — unlock with ${capability.credential}${capability.freeFallback !== undefined && humanFreeFallback(capability.freeFallback) ? `; free alternative: ${humanFreeFallback(capability.freeFallback)}` : ''}`,
1111
1095
  );
1112
1096
  return [
1113
- '할 수 있는 일:',
1097
+ 'Available:',
1114
1098
  ...(summary.available.length === 0 ? [' None.'] : summary.available.map((capability) => ` ${capability.name} — unlocked by ${capability.credential}`)),
1115
- '못 하는 일:',
1099
+ 'Unavailable:',
1116
1100
  ...(unavailable.length === 0 ? [' None.'] : unavailable),
1117
1101
  'Unknown by credential:',
1118
1102
  ...(summary.unknownCredentials.length === 0 ? [' Empty.'] : summary.unknownCredentials.map((credential) => ` ${credential}`)),
@@ -1129,8 +1113,8 @@ export function formatDoctorReport(report: DoctorReport, options: { credentials?
1129
1113
  const satisfied = report.credentials.filter((credential) => !credential.resolved && credential.satisfiedBy !== undefined).length;
1130
1114
  return [
1131
1115
  ...formatReadiness(report.readiness),
1132
- ...(requiredMissing.length ? [`⛔ 필수 명령 없음: ${requiredMissing.join(', ')}`] : []),
1133
- `자격증명: ${resolved}/${report.credentials.length} resolved${satisfied ? ` · ${satisfied} satisfied by alternative names` : ''} · details: elanous doctor --credentials`,
1116
+ ...(requiredMissing.length ? [`⛔ Required commands missing: ${requiredMissing.join(', ')}`] : []),
1117
+ `Credentials: ${resolved}/${report.credentials.length} resolved${satisfied ? ` · ${satisfied} satisfied by alternative names` : ''} · details: elanous doctor --credentials`,
1134
1118
  ...(options.credentials ? report.credentials.map((credential) => [
1135
1119
  `${credential.name}: ${credential.resolved ? 'resolved' : 'unresolved'} (${credential.source}) — ${credential.note}`,
1136
1120
  ...(credential.satisfiedBy !== undefined ? [` Satisfied by: ${credential.satisfiedBy.name} (${credential.satisfiedBy.source})`] : []),
@@ -1138,14 +1122,15 @@ export function formatDoctorReport(report: DoctorReport, options: { credentials?
1138
1122
  ...(credential.freeFallback !== undefined ? [` Free fallback${credential.freeFallbackMode === 'auto' || credential.freeFallbackMode === 'manual' || credential.freeFallbackMode === 'none' ? ` [${credential.freeFallbackMode}]` : ''}: ${humanFreeFallback(credential.freeFallback)}`] : []),
1139
1123
  ].join('\n')) : []),
1140
1124
  ...(report.catalogMetadataUnavailable ? ['Catalog metadata unavailable: could not read catalog/resources.yaml.'] : []),
1141
- ...(report.retiredConfigKeys ?? []).map(({ path, reason }) => `더는 안 쓰는 설정 키: ${path} — ${reason}`),
1125
+ ...(report.retiredConfigKeys ?? []).map(({ path, reason }) => `Retired config key: ${path} — ${reason}`),
1142
1126
  'External commands:',
1143
1127
  ...report.externalCommands.map((command) => {
1144
1128
  const breaks = command.status === 'broken' ? command.breaks_broken ?? command.breaks : command.breaks;
1129
+ const visibleBreaks = breaks === undefined ? undefined : humanBreaks(breaks);
1145
1130
  const fix = command.status === 'broken' ? command.fix_broken ?? command.fix : command.fix;
1146
1131
  return [
1147
1132
  `${command.name}: ${command.status === 'skipped' ? `skipped (${command.platform}-only)` : `${command.status} (${command.tier})`}${command.detail === undefined ? '' : ` — ${command.detail}`}`,
1148
- ...((command.status === 'missing' || command.status === 'broken') && breaks !== undefined ? [` Breaks: ${breaks}`] : []),
1133
+ ...((command.status === 'missing' || command.status === 'broken') && visibleBreaks !== undefined ? [` Breaks: ${visibleBreaks}`] : []),
1149
1134
  ...((command.status === 'missing' || command.status === 'broken') && fix !== undefined ? [` Fix: ${fix}`] : []),
1150
1135
  ].join('\n');
1151
1136
  }),
@@ -1260,13 +1245,12 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1260
1245
  home: deps.home,
1261
1246
  cacheDir: deps.cacheDir,
1262
1247
  readdir: deps.readdir,
1263
- readiness: deps.readiness ?? resolveReadinessDeps({
1248
+ readiness: deps.readiness ?? { ...resolveReadinessDeps({
1264
1249
  env: deps.env ?? process.env,
1265
1250
  pathDelimiter: deps.pathDelimiter ?? delimiter,
1266
1251
  platform: deps.platform ?? process.platform,
1267
1252
  commandExists: deps.commandExists ?? ((name) => (deps.env ?? process.env).PATH?.split(deps.pathDelimiter ?? delimiter).some((dir) => existsSync(join(dir, name))) ?? false),
1268
- getConfig: deps.getUserConfig ?? getUserConfig,
1269
- listAuthProviders: deps.listAuthProviders ?? defaultListAuthProviders,
1253
+ getConfig: () => deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)(),
1270
1254
  codeRevision: deps.codeRevision ?? defaultCodeRevision,
1271
1255
  fetchHealth: deps.fetchHealth ?? (() => defaultFetchHealth(deps.resolveDaemonEndpoint ?? resolveDaemonEndpoint)),
1272
1256
  readInstallPrefix: deps.readInstallPrefix ?? (() => defaultReadInstallPrefix(deps.repositoryRoot ?? repositoryRoot, deps.exists ?? existsSync)),
@@ -1274,13 +1258,15 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1274
1258
  // gh 판은 수리 계획에 쓰인다(낡은 gh → 정적 gh) — 🩸 09-25: 여기서 안 재서 계획은 보고서에만 뜨고 적용이 안 됐다.
1275
1259
  ghVersion: deps.ghVersion ?? (() => defaultGhVersion((name) => (deps.env ?? process.env).PATH?.split(deps.pathDelimiter ?? delimiter).some((dir) => existsSync(join(dir, name))) ?? false)),
1276
1260
  tmpdirSameFsAsBunCache: deps.tmpdirSameFsAsBunCache ?? (() => defaultTmpdirSameFsAsBunCache(deps.env ?? process.env)),
1277
- // 보고서와 같은 자로 — 로그인도 LLM 키도 없으면 계획에 «사람 한 줄»(로그인)이 실린다(🅢 #20263 ③).
1278
- ...(source.ok ? { llmKeyResolved: llmKeyResolvedFrom(source.credentials), nodePty: nodePtyStatus(source.externalCommands) } : {}),
1261
+ ...(source.ok ? { nodePty: nodePtyStatus(source.externalCommands) } : {}),
1279
1262
  // L0 칸(substrate·docker·kubernetes·memory)은 «정보성·선택 기능»이라 수리 계획에 싣지 않는다 —
1280
1263
  // `--sudo` 가 `sudo systemctl start docker` 를 «설치 줄»로 치지 않게 하고, docker·kubectl 탐침(최대 수 초)을 두 번 돌리지 않는다.
1281
1264
  // 보고서(`runDoctor`)에는 그대로 나온다.
1282
1265
  probeHostEnvironment: () => null,
1283
- }),
1266
+ }), usableLlm: (() => {
1267
+ try { return deps.resolveLlm ? deps.resolveLlm() : resolveUsableLlm({ config: deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)() }); }
1268
+ catch { return null; }
1269
+ })() },
1284
1270
  exists: deps.exists,
1285
1271
  readFile: deps.readFile,
1286
1272
  writeFile: deps.writeFile,
@@ -1374,16 +1360,16 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1374
1360
  else out.log([
1375
1361
  formatDoctorReport(currentReport, { credentials: opts.credentials }),
1376
1362
  ...(advice ? [formatDoctorAdvice(advice)] : []),
1377
- '수정 계획:',
1363
+ 'Fix plan:',
1378
1364
  ...plan.items.map((item) => ` ${item.id}: ${item.status} — ${item.path} — ${item.action}${item.reason ? ` — ${item.reason}` : ''}`),
1379
1365
  ...plan.manual.map((item) => ` ${item.id}: manual${item.remedy ? ` — ${item.remedy}` : ''}`),
1380
1366
  ...manualCommandFixes(currentReport).map((line) => ` ${line}`),
1381
- ...(gitInstall ? [`git 설치: ${gitInstall.detail}${gitPlan?.note ? ` — ${gitPlan.note}` : ''}`] : []),
1367
+ ...(gitInstall ? [`Git installation: ${gitInstall.detail}${gitPlan?.note ? ` — ${gitPlan.note}` : ''}`] : []),
1382
1368
  ...(sudoResult ? [sudoResult.sudoAvailable
1383
- ? `sudo 설치: ${sudoResult.runs.length ? sudoResult.runs.map((entry) => `${entry.result} — ${entry.command}${entry.detail ? ` — ${entry.detail}` : ''}`).join(' · ') : '칠 줄 없음'}`
1384
- : 'sudo 설치: 건너뜀 — 이 기계는 sudo 에 암호가 필요하다(sudo -n true 실패). 위 «manual» 줄을 직접 치세요.'] : []),
1385
- ...(results ? ['적용 결과:', ...results.items.map((item) => ` ${item.id}: ${item.result} — ${item.path}${item.reason ? ` — ${item.reason}` : ''}`)] : ['적용하려면 --fix --yes']),
1386
- ...(restartResult ? [`서비스 재시작: ${restartResult.result} — ${restartResult.reason}`] : []),
1369
+ ? `Sudo installation: ${sudoResult.runs.length ? sudoResult.runs.map((entry) => `${entry.result} — ${entry.command}${entry.detail ? ` — ${entry.detail}` : ''}`).join(' · ') : '칠 줄 없음'}`
1370
+ : 'Sudo installation: 건너뜀 — 이 기계는 sudo 에 암호가 필요하다(sudo -n true 실패). 위 «manual» 줄을 직접 치세요.'] : []),
1371
+ ...(results ? ['Applied:', ...results.items.map((item) => ` ${item.id}: ${item.result} — ${item.path}${item.reason ? ` — ${item.reason}` : ''}`)] : ['적용하려면 --fix --yes']),
1372
+ ...(restartResult ? [`Service restart: ${restartResult.result} — ${restartResult.reason}`] : []),
1387
1373
  ].join('\n'));
1388
1374
  const exitCode = Math.max(results?.exitCode ?? 0, sudoResult?.exitCode ?? 0, restartResult?.result === 'failed' ? 1 : 0, currentReport.requiredMissing?.length ? 1 : 0, gitInstall?.ran && !gitInstall.ok ? 1 : 0);
1389
1375
  if (results || exitCode) setExitCode(exitCode);
@@ -1,4 +1,5 @@
1
1
  import { remediesFor, type DistroFamily } from './doctor-distro.js';
2
+ import type { UsableLlm } from '../llm/usable-llm.js';
2
3
  import { providerSecretNames } from '../nexus/install/launchd.js';
3
4
  import { win32 } from 'node:path';
4
5
  /**
@@ -42,13 +43,8 @@ export interface ReadinessDeps {
42
43
  * must pass `null` rather than omit it).
43
44
  */
44
45
  provider?: string | null;
45
- /**
46
- * Codex subscription login is present. `null` means not measured.
47
- * The token itself is never passed in.
48
- */
49
- codexLogin?: boolean | null;
50
- /** LLM 을 부를 자격이 «하나라도» 있나(어떤 로그인이든 · LLM 키가 풀렸든). 없으면(undefined/null) 못 쟀다. */
51
- llmCredentialAvailable?: boolean | null;
46
+ /** Selected runtime route, already resolved with the runtime auto selector. null = unmeasured. */
47
+ usableLlm?: UsableLlm | null;
52
48
  /** `gh` is on PATH. `null` means not measured. */
53
49
  ghOnPath?: boolean | null;
54
50
  /** `gh --version` 의 판 · null/undefined = 못 쟀다. */
@@ -210,23 +206,19 @@ function providerDecision(deps: ReadinessDeps): ReadinessItem {
210
206
  return item('provider-decision', 'unknown', 'llm.provider was not measured');
211
207
  }
212
208
  const provider = deps.provider.trim() || 'auto';
213
- if (provider !== 'auto') {
214
- // 아는 provider 이름만 보여 준다 — 설정 칸에 자격 같은 값이 들어와 있어도 보고에 싣지 않는다.
215
- const shown = KNOWN_PROVIDERS.has(provider) ? provider : '(set · value not shown)';
216
- return item('provider-decision', 'ok', `llm.provider=${shown}`);
217
- }
218
- if (deps.codexLogin === undefined || deps.codexLogin === null) {
219
- return item('provider-decision', 'unknown', 'llm.provider=auto but codex login was not measured');
220
- }
221
- if (deps.codexLogin === true) {
222
- // 2026-09-24 결정(`#20142`) — auto 는 codex 구독을 먼저 쓰고 계정 회전·llm.fallbackChain 이 뒤를 잇는다.
223
- return item('provider-decision', 'ok', 'llm.provider=auto uses the codex login first; account rotation and llm.fallbackChain apply');
224
- }
225
- // 🩸 2026-09-24 빈 VM(🅢 #20263): 로그인도 키도 없는데 여기서 `ok` 가 나왔다(거짓 초록) — auto 가 고를 것이 없다.
226
- if (deps.llmCredentialAvailable === false) {
227
- return item('provider-decision', 'manual', 'llm.provider=auto but no LLM login or key was found — LLM commands will fail', 'elanous login openai-codex');
228
- }
229
- return item('provider-decision', 'ok', 'llm.provider=auto and no codex login');
209
+ const shown = KNOWN_PROVIDERS.has(provider) ? provider : '(set · value not shown)';
210
+ if (deps.usableLlm == null) return item('provider-decision', 'unknown', `llm.provider=${shown} route was not measured`);
211
+ const { usable, provider: selected, via, why } = deps.usableLlm;
212
+ if (!usable) {
213
+ const reason = why === 'no usable LLM route selected' ? why : 'route unavailable';
214
+ const remedy = provider === 'auto' || provider === 'openai-codex' ? 'elanous login openai-codex'
215
+ : provider === 'openai' ? 'Set OPENAI_API_KEY or llm.apiKey for openai'
216
+ : provider === 'local' ? 'Set llm.baseUrl or LOCAL_LLM_URL for local'
217
+ : KNOWN_PROVIDERS.has(provider) ? `Configure credentials for llm.provider=${provider}` : undefined;
218
+ return item('provider-decision', 'manual', `llm.provider=${shown} selected no usable LLM (via none) — ${reason}`, remedy);
219
+ }
220
+ const selectedName = selected && KNOWN_PROVIDERS.has(selected) ? selected : 'selected provider';
221
+ return item('provider-decision', 'ok', `llm.provider=${shown} → ${via} ${selectedName}`);
230
222
  }
231
223
 
232
224
  /** Debian 계열 apt 의 gh 는 GH_MIN_VERSION 미만이다(📏 09-25: Debian 12 = 2.23 · Ubuntu 24.04 = 2.45) — apt 로 깔면 곧바로
@@ -282,7 +282,7 @@ export function registerIntakeCommands(program: Command): void {
282
282
  const { sendTelegramReport } = await import('../telegram-report.js');
283
283
  const { getUserConfig } = await import('../user-config.js');
284
284
  const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
285
- const sent = await sendTelegramReport(getUserConfig(), text, { markdown: true });
285
+ const sent = await sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
286
286
  debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
287
287
  console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
288
288
  if (!sent) process.exitCode = 3;
@@ -1,6 +1,7 @@
1
1
  import { resolveDaemonEndpoint, type ResolveDaemonEndpointOpts } from '../nexus/daemon-endpoint.js';
2
2
  import { RemotesStore } from './remotes.js';
3
3
  import { bookmarkAttachDefaults } from './remote-resolve.js';
4
+ import { getNodeKind } from '../graph-kinds/registry.js';
4
5
 
5
6
  export type McpCliClassification =
6
7
  | 'ok'
@@ -84,7 +85,15 @@ export async function callDaemonMcpTool(opts: {
84
85
  arguments: Record<string, unknown>;
85
86
  endpoint?: string | ResolvedEndpoint;
86
87
  fetchFn?: McpCallOpts['fetchFn'];
88
+ pluginKind?: string;
87
89
  }): Promise<unknown> {
90
+ if (opts.pluginKind) {
91
+ const entry = getNodeKind('workflow', opts.pluginKind);
92
+ if (!entry || entry.core || !entry.plugin || !entry.run || !('mcp' in entry.run)
93
+ || opts.tool !== `${entry.run.mcp.server}.${entry.run.mcp.tool}`) {
94
+ throw new McpCliFailure('mcp-usage-error', 'invalid plugin MCP kind');
95
+ }
96
+ }
88
97
  const endpoint = typeof opts.endpoint === 'string' ? { url: opts.endpoint } : opts.endpoint;
89
98
  const resolved = endpoint ? { ok: true as const, value: endpoint } : resolveMcpEndpoint({});
90
99
  if (!resolved.ok) throw new McpCliFailure(resolved.classification, resolved.message);