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
@@ -18,8 +18,12 @@ import { tmpdir } from 'node:os';
18
18
  import { dirname, join, resolve } from 'node:path';
19
19
  import type { Command } from 'commander';
20
20
  import { debug } from '../debug/log.js';
21
- import { effectiveInstanceRoot } from '../instance/resolve.js';
21
+ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
22
+ import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
22
23
  import { envLiteral } from '../platform/env-literal.js';
24
+ import { userConfigPath } from '../user-config.js';
25
+ import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition } from '../release-loop/checklist.js';
26
+ import { writeStdoutJson } from './stdout-json.js';
23
27
 
24
28
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
25
29
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -257,7 +261,7 @@ export async function tagRelease(opts: TagOptions, run: Runner = defaultRunner):
257
261
  return { tagged: true };
258
262
  }
259
263
 
260
- export async function publishRelease(opts: { dir: string; notesFile: string; yes?: boolean; repoRoot?: string; instanceRoot?: string; log?: (l: string) => void }, run: Runner = defaultRunner): Promise<{ published: boolean; steps: PublishStep[] }> {
264
+ export async function publishRelease(opts: { dir: string; notesFile: string; yes?: boolean; repoRoot?: string; instanceRoot?: string; ledgerRoot?: string; log?: (l: string) => void }, run: Runner = defaultRunner): Promise<{ published: boolean; steps: PublishStep[] }> {
261
265
  const log = opts.log ?? ((l: string) => console.log(l));
262
266
  const m = readManifest(opts.dir);
263
267
  if (!existsSync(opts.notesFile)) throw new Error(`릴리스 본문 파일이 없다: ${opts.notesFile} (초안: ${m.notesDraft ?? 'release notes --from <ref>'})`);
@@ -273,11 +277,24 @@ export async function publishRelease(opts: { dir: string; notesFile: string; yes
273
277
  checkSourceTag(m.version, m.sourceCommit, repoRoot, run);
274
278
  for (const s of steps) must(run(s.command, s.args, s.cwd), s.what);
275
279
  applySourceTag(m.version, m.sourceCommit, repoRoot, run);
276
- const recordDir = join(opts.instanceRoot ?? effectiveInstanceRoot(), 'release', m.version);
277
- mkdirSync(recordDir, { recursive: true });
278
- const record = join(recordDir, 'release.json');
279
- writeFileSync(record, `${JSON.stringify({ ...m, publishedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
280
- chmodSync(record, 0o600);
280
+ // 판 기록은 이 우주 ⊕ 기계 단위 릴리스 원장(`releaseLedgerRoot()` · 다음 판 gate 가 기준선으로 읽는 자리) 두 곳에 남긴다.
281
+ // 공개는 이미 끝났으니 한 자리가 실패해도 다른 자리는 쓰고, 실패는 숨기지 않고 알린다(되돌릴 수 없는 단계 뒤라 던지지 않는다).
282
+ const body = `${JSON.stringify({ ...m, publishedAt: new Date().toISOString() }, null, 2)}\n`;
283
+ // 설정 디렉터리를 명시로 바꿔 끼운 경우(시험·`--config-dir`)는 그 우주만 — 다른 원장으로 넘지 않는다(gate 의 `releaseLedgerRoot` 와 같은 규칙).
284
+ const roots = getElanousConfigDirOverride() ? [effectiveInstanceRoot()]
285
+ : [...new Set([opts.instanceRoot ?? effectiveInstanceRoot(), opts.ledgerRoot ?? releaseLedgerRoot()])];
286
+ for (const root of roots) {
287
+ const record = join(root, 'release', m.version, 'release.json');
288
+ try {
289
+ mkdirSync(dirname(record), { recursive: true });
290
+ const temp = `${record}.${process.pid}.tmp`;
291
+ writeFileSync(temp, body, { mode: 0o600 });
292
+ chmodSync(temp, 0o600);
293
+ renameSync(temp, record);
294
+ } catch (error) {
295
+ log(`⚠ 판 기록을 못 남겼다: ${record} — ${error instanceof Error ? error.message : String(error)} (공개는 끝났다 · 다음 판 gate 는 기준선을 다시 스윕한다)`);
296
+ }
297
+ }
281
298
  log(`✅ 공개: https://github.com/${m.publicRepo}/releases/tag/${m.tag} — 이어서 \`elanous release verify --version ${m.version}\``);
282
299
  return { published: true, steps };
283
300
  }
@@ -436,8 +453,81 @@ async function jsonAction<T>(json: boolean | undefined, body: (log: (l: string)
436
453
  }
437
454
  }
438
455
 
456
+ function checklistCodenames(): Record<string, string> {
457
+ const path = getElanousConfigDirOverride() ? join(effectiveInstanceRoot(), 'config.json') : userConfigPath();
458
+ if (!existsSync(path)) return {};
459
+ const raw = JSON.parse(readFileSync(path, 'utf8')) as { release?: { codenames?: Record<string, string> } };
460
+ return raw.release?.codenames ?? {};
461
+ }
462
+
463
+ function checklistVersion(input: string | undefined, codenames: Record<string, string>): string {
464
+ const v = input ?? devVersion().replace(/-dev\.\d+$/, '');
465
+ const resolved = isReleaseVersion(v) ? v : Object.entries(codenames).find(([, name]) => name === v)?.[0] ?? v;
466
+ if (!isReleaseVersion(resolved)) throw new Error(`체크리스트 판 또는 별칭이 아니다: ${v}`);
467
+ return resolved;
468
+ }
469
+
470
+ async function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): Promise<void> {
471
+ const data = listChecklist(v);
472
+ const summary = summarizeChecklist(data);
473
+ const result = { ...data, codename: codenames[v] ?? '', ...summary };
474
+ if (json) { await writeStdoutJson(`${JSON.stringify(result)}\n`); return; }
475
+ console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
476
+ if (mode === 'list') {
477
+ for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
478
+ } else {
479
+ console.log(`🟢 ${summary.green} · 🟡 ${summary.yellow} · 🔴 ${summary.red} · ✅ ${summary.done}`);
480
+ console.log(`🔴 칸: ${summary.blocked.join(', ') || '없음'}`);
481
+ console.log(`담당별: ${Object.entries(summary.byOwner).map(([owner, count]) => `${owner} ${count}`).join(' · ') || '없음'}`);
482
+ }
483
+ }
484
+
439
485
  export function registerReleaseCommands(program: Command): void {
440
486
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
487
+ const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
488
+ .option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
489
+ .option('--json', '결과 JSON');
490
+ const context = (cmd: Command) => {
491
+ const opts = { ...(cmd.parent?.opts() as { version?: string; json?: boolean }), ...(cmd.opts() as { version?: string; json?: boolean }) };
492
+ const codenames = checklistCodenames();
493
+ return { version: checklistVersion(opts.version, codenames), codenames, json: opts.json };
494
+ };
495
+ const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON');
496
+ withContext(checklist.command('list').description('모든 칸')).action(async (_opts: unknown, cmd: Command) => {
497
+ const { version, codenames, json } = context(cmd);
498
+ await checklistOutput(version, codenames, 'list', json);
499
+ });
500
+ withContext(checklist.command('status').description('상태 요약')).action(async (_opts: unknown, cmd: Command) => {
501
+ const { version, codenames, json } = context(cmd);
502
+ await checklistOutput(version, codenames, 'status', json);
503
+ });
504
+ withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').action((id: string, title: string, opts: { owner?: string }, cmd: Command) => {
505
+ const { version, json } = context(cmd);
506
+ const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}) });
507
+ if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
508
+ });
509
+ withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
510
+ .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
511
+ .option('--disposition <disposition>', 'move|known-issue|block')
512
+ .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string }, cmd: Command) => {
513
+ const { version, json } = context(cmd);
514
+ if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new Error(`잘못된 상태: ${opts.status}`);
515
+ if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new Error(`잘못된 처분: ${opts.disposition}`);
516
+ if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined) throw new Error('갱신할 칸을 지정하라');
517
+ const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}) }, process.env.ELANOUS_TRACK || 'cli');
518
+ if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 갱신`);
519
+ });
520
+ withContext(checklist.command('rm <id>').description('칸 삭제')).action((id: string, _opts: unknown, cmd: Command) => {
521
+ const { version, json } = context(cmd);
522
+ const data = removeItem(version, id, process.env.ELANOUS_TRACK || 'cli');
523
+ if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 삭제`);
524
+ });
525
+ withContext(checklist.command('seed').description('마크다운 로드맵에서 빈 칸 들이기')).requiredOption('--from <file>', '로드맵 파일')
526
+ .action((opts: { from: string }, cmd: Command) => {
527
+ const { version, json } = context(cmd);
528
+ const data = seedFromRoadmap(version, readFileSync(opts.from, 'utf8'));
529
+ if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${version} ${data.items.length}칸`);
530
+ });
441
531
  release.command('prepare')
442
532
  .description('깨끗한 원본 → 공개본 → 공개 저장소 이력 위 커밋 → PWA → 묶음·체크섬 → 로컬 끝까지 (네트워크 쓰기 없음)')
443
533
  .requiredOption('--version <x.y.z>', 'package.json 과 같은 버전(먼저 버전 PR 을 착지)')
@@ -235,11 +235,11 @@ export function formatUpdateNotice(result: Pick<SelfUpdateResult, 'installedVers
235
235
  function defaultNotice(text: string): void {
236
236
  // 시험에선 텔레그램으로 나가지 않는다.
237
237
  if (process.env.NODE_ENV === 'test') return;
238
- import('../domains/outbound-alert.js').then((m) => { m.sendOutbound(text, 'report'); }).catch(() => {});
238
+ import('../domains/outbound-alert.js').then((m) => { m.sendOutbound(text, 'ops-report'); }).catch(() => {});
239
239
  }
240
240
 
241
241
  function defaultAlert(text: string): void {
242
- import('../domains/outbound-alert.js').then((m) => { m.sendOutbound(text, 'alert'); }).catch(() => {});
242
+ import('../domains/outbound-alert.js').then((m) => { m.sendOutbound(text, 'ops-alert'); }).catch(() => {});
243
243
  }
244
244
 
245
245
  export interface VersionPruneOutcome {
@@ -296,8 +296,11 @@ export function childPath(env: NodeJS.ProcessEnv = process.env, execPath: string
296
296
  return current.split(':').includes(bunDir) ? current : [bunDir, current].filter(Boolean).join(':');
297
297
  }
298
298
 
299
+ // bun 1.4 는 maxBuffer(기본 1MiB)를 넘는 출력을 자르고 자식을 죽인다 — 설치·빌드 로그는 그보다 클 수 있다.
300
+ const SPAWN_MAX_BUFFER = 64 * 1024 * 1024;
301
+
299
302
  const execute = (command: string, args: string[], cwd: string, options?: { timeout?: number }) => {
300
- const result = spawnSync(command, args, { cwd, encoding: 'utf8', timeout: options?.timeout, maxBuffer: options?.timeout ? 64 * 1024 * 1024 : undefined, env: { ...process.env, PATH: childPath() } });
303
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8', timeout: options?.timeout, maxBuffer: SPAWN_MAX_BUFFER, env: { ...process.env, PATH: childPath() } });
301
304
  return { status: result.status, stdout: result.stdout ?? '', stderr: result.error?.message ?? result.stderr ?? '' };
302
305
  };
303
306
 
@@ -395,7 +398,7 @@ export async function runReleaseUpdate(options: ReleaseUpdateOptions = {}, deps:
395
398
  }
396
399
  } catch { previous = ''; }
397
400
  const run = deps.run ?? ((command: string, args: string[], cwd: string, input?: string, env?: NodeJS.ProcessEnv) => {
398
- const result = spawnSync(command, args, { cwd, encoding: 'utf8', input, env: { ...process.env, ...env, PATH: childPath() } });
401
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8', input, maxBuffer: SPAWN_MAX_BUFFER, env: { ...process.env, ...env, PATH: childPath() } });
399
402
  return { status: result.status, stderr: result.error?.message ?? result.stderr ?? '' };
400
403
  });
401
404
  try {
@@ -1,7 +1,8 @@
1
1
  import { spawn, type SpawnOptions } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline/promises';
3
3
  import type { Command } from 'commander';
4
- import { detectProviders, type DetectedProvider } from '../llm/provider-detect.js';
4
+ import type { DetectedProvider } from '../llm/provider-detect.js';
5
+ import { resolveUsableLlm, type UsableLlm } from '../llm/usable-llm.js';
5
6
  import { resolveDaemonEndpoint, type DaemonEndpoint, type ResolveDaemonEndpointOpts } from '../nexus/daemon-endpoint.js';
6
7
  import { runBgLaunch } from './bg-launch.js';
7
8
 
@@ -13,10 +14,13 @@ export interface StartOptions {
13
14
  }
14
15
 
15
16
  export interface StartState {
16
- providers: readonly Pick<DetectedProvider, 'provider' | 'auth' | 'available' | 'rank'>[];
17
+ /** Discovery candidates are diagnostic only; the resolved route controls the plan. */
18
+ providers?: readonly Pick<DetectedProvider, 'provider' | 'auth' | 'available' | 'rank'>[];
17
19
  tty: boolean;
18
20
  healthy: boolean;
19
21
  discoveryFailed?: boolean;
22
+ /** Required selected runtime route; null means resolution failed, not an absent LLM. */
23
+ llm: UsableLlm | null;
20
24
  }
21
25
 
22
26
  export type StartStepId = 'detect-llm' | 'login' | 'check-daemon' | 'launch-daemon' | 'open-gui' | 'open-tui';
@@ -32,11 +36,13 @@ export interface StartResult {
32
36
 
33
37
  /** No I/O and no credential values: the same observations always produce the same actions. */
34
38
  export function planStart(options: StartOptions, state: StartState): StartPlannedStep[] {
35
- const available = state.providers.some((entry) => entry.available && entry.auth !== 'agent-cli');
39
+ const available = state.llm?.usable === true;
40
+ // The offered login is the Codex device login: it only fixes the auto route or an explicit openai-codex.
41
+ const codexLoginFixes = state.llm?.provider === undefined || state.llm.provider === 'openai-codex';
36
42
  const gui = options.gui === true || options.tui !== true;
37
43
  return [
38
44
  { id: 'detect-llm', needed: true },
39
- { id: 'login', needed: !state.discoveryFailed && !available && state.tty && options.json !== true && options.login !== false },
45
+ { id: 'login', needed: !state.discoveryFailed && state.llm !== null && !available && codexLoginFixes && state.tty && options.json !== true && options.login !== false },
40
46
  { id: 'check-daemon', needed: true },
41
47
  { id: 'launch-daemon', needed: !state.healthy },
42
48
  { id: 'open-gui', needed: gui },
@@ -45,7 +51,9 @@ export function planStart(options: StartOptions, state: StartState): StartPlanne
45
51
  }
46
52
 
47
53
  export interface StartDeps {
54
+ /** Optional diagnostic seam; never gates runtime route selection. */
48
55
  detect?: () => Promise<DetectedProvider[]>;
56
+ resolveLlm?: () => UsableLlm;
49
57
  isTty?: () => boolean;
50
58
  confirmLogin?: () => Promise<boolean>;
51
59
  login?: () => Promise<boolean>;
@@ -115,21 +123,25 @@ export async function runStart(options: StartOptions = {}, deps: StartDeps = {})
115
123
  const output = deps.output ?? console.log;
116
124
  const steps: StartStep[] = [];
117
125
  const record = (id: StartStepId, status: StartStep['status'], detail: string) => steps.push({ id, status, detail });
118
- let providers: DetectedProvider[] = [];
126
+ let usable: UsableLlm | undefined;
119
127
  let discoveryFailed = false;
128
+ if (deps.detect) {
129
+ try { await deps.detect(); }
130
+ catch { /* Diagnostic candidate probe must not gate the runtime route. */ }
131
+ }
120
132
  try {
121
- providers = await (deps.detect ?? detectProviders)();
122
- record('detect-llm', 'done', 'LLM discovery finished');
133
+ usable = (deps.resolveLlm ?? resolveUsableLlm)();
134
+ record('detect-llm', 'done', 'LLM route resolved');
123
135
  } catch {
124
136
  discoveryFailed = true;
125
- record('detect-llm', 'failed', 'LLM discovery failed');
137
+ record('detect-llm', 'failed', 'LLM route could not be resolved');
126
138
  }
127
139
  const tty = (deps.isTty ?? (() => process.stdin.isTTY === true))();
128
140
  let healthy = false;
129
141
  const probeHealth = deps.health ?? (() => defaultHealth(deps));
130
142
  try { healthy = await probeHealth(); } catch { /* No health response; try launch. */ }
131
- const plan = planStart(options, { providers, tty, healthy, discoveryFailed });
132
- let llm: StartResult['llm'] = discoveryFailed ? 'unknown' : providers.some((entry) => entry.available && entry.auth !== 'agent-cli') ? 'available' : 'missing';
143
+ const plan = planStart(options, { tty, healthy, discoveryFailed, llm: usable ?? null });
144
+ let llm: StartResult['llm'] = discoveryFailed ? 'unknown' : usable?.usable ? 'available' : 'missing';
133
145
  if (plan.find((entry) => entry.id === 'login')?.needed) {
134
146
  let agreed = false;
135
147
  try { agreed = await (deps.confirmLogin ?? confirmLogin)(); } catch { /* No consent. */ }
@@ -137,8 +149,12 @@ export async function runStart(options: StartOptions = {}, deps: StartDeps = {})
137
149
  try {
138
150
  if (await (deps.login ?? defaultLogin)()) {
139
151
  try {
140
- providers = await (deps.detect ?? detectProviders)();
141
- llm = providers.some((entry) => entry.available && entry.auth !== 'agent-cli') ? 'available' : 'missing';
152
+ if (deps.detect) {
153
+ try { await deps.detect(); }
154
+ catch { /* Runtime route decides. */ }
155
+ }
156
+ usable = (deps.resolveLlm ?? resolveUsableLlm)();
157
+ llm = usable.usable ? 'available' : 'missing';
142
158
  record('login', llm === 'available' ? 'done' : 'failed', llm === 'available' ? 'LLM login detected' : 'No usable LLM login detected');
143
159
  } catch {
144
160
  llm = 'unknown';
@@ -147,7 +163,8 @@ export async function runStart(options: StartOptions = {}, deps: StartDeps = {})
147
163
  } else record('login', 'failed', 'LLM login did not complete');
148
164
  } catch { record('login', 'failed', 'LLM login or discovery did not complete'); }
149
165
  } else record('login', 'skipped', 'Login declined');
150
- } else record('login', 'skipped', llm === 'available' ? 'LLM already available' : llm === 'unknown' ? 'LLM discovery unavailable; login not offered' : 'Interactive login unavailable');
166
+ } else record('login', 'skipped', llm === 'available' ? 'LLM already available' : llm === 'unknown' ? 'LLM discovery unavailable; login not offered'
167
+ : usable?.provider !== undefined && usable.provider !== 'openai-codex' ? `Codex login does not configure llm.provider=${usable.provider}` : 'Interactive login unavailable');
151
168
  record('check-daemon', healthy ? 'done' : 'failed', healthy ? 'Daemon healthy' : 'Daemon did not respond');
152
169
  let daemon: StartResult['daemon'] = healthy ? 'running' : 'failed';
153
170
  if (plan.find((entry) => entry.id === 'launch-daemon')?.needed) {
@@ -197,7 +214,9 @@ export async function runStart(options: StartOptions = {}, deps: StartDeps = {})
197
214
  if (options.json) output(JSON.stringify(result));
198
215
  else {
199
216
  for (const step of steps) output(`${step.status === 'done' ? '✓' : step.status === 'failed' ? '✗' : '–'} ${step.id}: ${step.detail}`);
200
- if (llm === 'missing') output('LLM not configured; run `elanous login openai-codex` or `elanous llm detect`.');
217
+ const selected = usable?.provider;
218
+ if (llm === 'missing' && selected !== undefined && selected !== 'openai-codex') output(`LLM not configured for llm.provider=${selected}; run \`elanous doctor\` for that provider's fix.`);
219
+ else if (llm === 'missing') output('LLM not configured; run `elanous login openai-codex` or `elanous llm detect`.');
201
220
  if (llm === 'unknown') output('LLM status unknown; discovery failed. Run `elanous llm detect` to check configuration.');
202
221
  }
203
222
  return result;
@@ -83,6 +83,8 @@ export interface DashboardAcpBootDeps {
83
83
  * callbacks become unused — the daemon's `runTurn` owns those.
84
84
  * `getCwd()` is still consulted to populate `newSession(cwd)`. */
85
85
  remote?: { url: string; token?: string; label?: string };
86
+ /** Notifies the dashboard when an established remote connection drops. */
87
+ onRemoteDisconnect?: () => void;
86
88
  /** Tier 1 daemon-resume — when set together with `remote`,
87
89
  * the boot flips from `DashboardSession.attach()`
88
90
  * (which mints a new sessionId) to `attachExisting({sessionId})`
@@ -205,6 +207,7 @@ export async function bootDashboardAcpSession(
205
207
  url: deps.remote!.url,
206
208
  ...(deps.remote!.token ? { token: deps.remote!.token } : {}),
207
209
  ...(deps.remote!.label ? { label: deps.remote!.label } : {}),
210
+ onDisconnect: () => deps.onRemoteDisconnect?.(),
208
211
  }),
209
212
  });
210
213
  recordDashboardAcpBootOutcome(result);
@@ -5,11 +5,14 @@
5
5
 
6
6
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
7
7
  import { join } from 'path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { createStaleInstallCheck, readCurrentInstalledCommit, readDashboardBootCommit, STALE_INSTALL_CHECK_INTERVAL_MS, versionedDashboardInstallDir } from './stale-install-check.js';
8
10
  import { spawn, spawnSync } from 'node:child_process';
9
11
  import { $ } from 'bun';
10
12
  import { LOCAL_SKILLS_DIR, SERVICE_NAMES, DATA_DIR, OBSIDIAN_VAULT } from '../config.js';
11
13
  import { dispatchDashboardSessionRuntimeTool } from './session-runtime-dispatch.js';
12
14
  import { buildAdRunSetup } from '../ad-pipeline/ad-run-setup.js';
15
+ import { nextChatScrollOffset } from '../chat/log-scroll-key.js';
13
16
  import type { CommandRunner } from '../ad-pipeline/higgsfield-backend.js';
14
17
  import type { ShootRunOptions } from '../ad-pipeline/shoot-run.js';
15
18
  import type { QcThresholds } from '../ad-pipeline/qc.js';
@@ -602,6 +605,7 @@ import {
602
605
  import { createDashboardInlineSetupFlow } from './setup-inline.js';
603
606
  import { resolveDashboardChatMainThemeCommand } from './input/chat-main-theme-command.js';
604
607
  import { resolveDashboardChatMainLogCommand } from './input/chat-main-log-command.js';
608
+ import { noProviderReply } from './input/no-provider-reply.js';
605
609
  import {
606
610
  dashboardLogHelpLines,
607
611
  resolveDashboardLogFilterAction,
@@ -1066,6 +1070,7 @@ import { bootDashboardHitl } from './hitl-boot.js';
1066
1070
  import { attachDashboardFilePathToken } from './file-path-attachment.js';
1067
1071
  import { bootDashboardSlashExecutor } from './slash-executor-boot.js';
1068
1072
  import { buildDashboardSlashRegistry, type DashboardSlashContext } from './slash-runtime/index.js';
1073
+ import { unknownSlashReply } from './slash-runtime/unknown-slash-reply.js';
1069
1074
  import { resolveSurfaceUx } from '../agent/surface-ux/build.js';
1070
1075
  import type { SurfaceUx } from '../agent/surface-ux/types.js';
1071
1076
  import { getDefaultQuestionChannels, type QuestionChannel } from '../hitl/question.js';
@@ -4239,6 +4244,36 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
4239
4244
  } catch { /* noop */ }
4240
4245
  }, 5_000);
4241
4246
  (gitDirtyTimer as unknown as { unref?: () => void }).unref?.();
4247
+ // The running module's installed version, not the user's working directory, is the baseline.
4248
+ const dashboardCodeRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
4249
+ const versionDir = versionedDashboardInstallDir(dashboardCodeRoot);
4250
+ const staleInstallCheck = createStaleInstallCheck({
4251
+ codeRoot: dashboardCodeRoot,
4252
+ bootCommit: versionDir ? readDashboardBootCommit(versionDir) : undefined,
4253
+ readInstalledCommit: readCurrentInstalledCommit,
4254
+ now: Date.now,
4255
+ });
4256
+ if (versionDir) {
4257
+ const checkInstall = (): void => {
4258
+ const result = staleInstallCheck.check();
4259
+ debug.log('dashboard.version', 'install-check', {
4260
+ state: result.state, bootCommit: result.bootCommit, installedCommit: result.installedCommit,
4261
+ });
4262
+ if (staleInstallCheck.shouldNotify(result)) {
4263
+ chatLines.push(C.warning(`elanous 가 새 판으로 바뀌었다 (${result.bootCommit!.slice(0, 7)} → ${result.installedCommit!.slice(0, 7)}) · TUI 를 다시 띄워라`));
4264
+ chatScrollOffset = -1;
4265
+ try { draw(); } catch { /* TUI torn down */ }
4266
+ }
4267
+ };
4268
+ checkInstall();
4269
+ const installCheckTimer = setInterval(checkInstall, STALE_INSTALL_CHECK_INTERVAL_MS);
4270
+ installCheckTimer.unref?.();
4271
+ } else {
4272
+ const result = staleInstallCheck.check();
4273
+ debug.log('dashboard.version', 'install-check', {
4274
+ state: result.state, bootCommit: result.bootCommit, installedCommit: result.installedCommit,
4275
+ });
4276
+ }
4242
4277
  // Phase T1 — unified terminal matrix. Wraps sessionRegistry during
4243
4278
  // the migration window: spawns flow through `matrix.spawn()` so we
4244
4279
  // get a single `term:<N>` id space, character/transport fields,
@@ -15343,7 +15378,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15343
15378
  ).persistToolOutputPreview;
15344
15379
  acpPtyAvailable = (await import('../pty-shell/registry.js')).ptyAvailable;
15345
15380
  const dashboardAcpBootResult = await bootDashboardAcpSession({
15346
- ...(opts.remote ? { remote: opts.remote } : {}),
15381
+ ...(opts.remote ? {
15382
+ remote: opts.remote,
15383
+ onRemoteDisconnect: () => {
15384
+ pushChatLine(C.warning(' ⚠ 데몬 연결이 끊겼다 · 입력 중인 글은 그대로다 · 다시 붙으려면 TUI 를 다시 띄워라'));
15385
+ draw();
15386
+ },
15387
+ } : {}),
15347
15388
  ...(opts.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {}),
15348
15389
  getCwd: () => getSessionCwd(),
15349
15390
  getChatHistory: () => chat.history,
@@ -16742,6 +16783,17 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16742
16783
  : '/quit /clear (run `elanous setup` — no LLM provider available)',
16743
16784
  debugLog: debug.enabled ? (event, label, payload) => debug.log(event, label, payload) : undefined,
16744
16785
  textInputOpts: {
16786
+ onScrollKey: (name) => {
16787
+ const { rows } = termSize();
16788
+ const viewport = computePromptFrameLogViewportBounds(
16789
+ rows, computePaneH(rows), getLayoutPromptFrame(rows),
16790
+ );
16791
+ chatScrollOffset = nextChatScrollOffset(
16792
+ chatScrollOffset, name, Math.max(1, viewport.height),
16793
+ Math.max(0, chatLines.length - viewport.height),
16794
+ );
16795
+ draw();
16796
+ },
16745
16797
  // TUI 부활 후속 — Esc·Esc = /rewind 픽커 (codex backtrack
16746
16798
  // 제스처 · resolveEscEscRewind 순수 판정). 빈 버퍼에서만
16747
16799
  // prime · 1.5s 창. 힌트는 세션 1회.
@@ -18453,17 +18505,9 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18453
18505
  const handled = await pluginHost.dispatchSlash(cmdLower, args);
18454
18506
  if (handled) { chatScrollOffset = -1; continue; }
18455
18507
  }
18456
- const retiredHarnessCommand = ({
18457
- ask: '/harness ask <무엇을 왜 고칠지 한 문장>',
18458
- say: '/harness ask <무엇을 왜 고칠지 한 문장>',
18459
- implement: '/harness implement <feature>',
18460
- dev: '/harness dev <무엇을 왜 고칠지 한 문장>',
18461
- } as Record<string, string>)[cmdLower];
18462
- if (retiredHarnessCommand) {
18463
- chatLines.push(C.warning(`/${cmdLower} has moved; use ${retiredHarnessCommand}`));
18464
- } else {
18465
- chatLines.push(C.warning(`Unknown command: /${cmdLower}`));
18466
- chatLines.push(C.muted('Type /help for the list of commands.'));
18508
+ const replyLines = unknownSlashReply(cmdLower, dashboardSlashRegistry.names());
18509
+ for (const [index, line] of replyLines.entries()) {
18510
+ chatLines.push(index === 0 ? C.warning(line) : C.muted(line));
18467
18511
  }
18468
18512
  chatScrollOffset = -1;
18469
18513
  continue; // stay in input mode
@@ -18475,7 +18519,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
18475
18519
 
18476
18520
  // ── Q&A chat — requires at least one LLM provider ──
18477
18521
  if (!anyProviderAvailable()) {
18478
- chatLines.push(C.warning('No LLM provider available. Run `elanous setup` or `elanous codex setup`.'));
18522
+ const reply = noProviderReply(input.text);
18523
+ for (const line of reply.lines) {
18524
+ chatLines.push(line.tone === 'warning' ? C.warning(line.text) : C.muted(line.text));
18525
+ }
18526
+ inputPrefixState.set(reply.prefill);
18527
+ nextInitial = inputPrefixState.consumeInitialText('plain');
18479
18528
  continue; // stay in input mode
18480
18529
  }
18481
18530
 
@@ -0,0 +1,13 @@
1
+ export function noProviderReply(text: string): {
2
+ lines: Array<{ tone: 'warning' | 'muted'; text: string }>;
3
+ prefill: string;
4
+ } {
5
+ const prefill = text.trim();
6
+ return {
7
+ lines: [
8
+ { tone: 'warning', text: 'No LLM provider available. Run `elanous setup` or `elanous codex setup`.' },
9
+ { tone: 'muted', text: '보내지 않았다 — 입력칸에 그대로 두었다 · setup 뒤 Enter' },
10
+ ],
11
+ prefill,
12
+ };
13
+ }
@@ -0,0 +1,78 @@
1
+ // Keep migration and retirement advice separate from the active slash registry:
2
+ // a retired name must never be advertised as an available command.
3
+ export const RETIRED_SLASH_ADVICE: Readonly<Record<string, { kind: 'moved' | 'retired'; use: string }>> = {
4
+ // These four entry points moved into the harness command.
5
+ ask: { kind: 'moved', use: '/harness ask <무엇을 왜 고칠지 한 문장>' },
6
+ say: { kind: 'moved', use: '/harness ask <무엇을 왜 고칠지 한 문장>' },
7
+ implement: { kind: 'moved', use: '/harness ask <무엇을 왜 고칠지 한 문장>' },
8
+ dev: { kind: 'moved', use: '/harness dev <무엇을 왜 고칠지 한 문장>' },
9
+ // Retired rich-UI and auxiliary commands from the slash registration tests.
10
+ ui: { kind: 'retired', use: '/help' }, workspace: { kind: 'retired', use: '/help' },
11
+ ws: { kind: 'retired', use: '/help' }, window: { kind: 'retired', use: '/help' },
12
+ win: { kind: 'retired', use: '/help' }, scratch: { kind: 'retired', use: '/help' },
13
+ sc: { kind: 'retired', use: '/help' }, 'acp-vw': { kind: 'retired', use: '/help' },
14
+ 'claude-vw': { kind: 'retired', use: '/help' }, 'pty-pane': { kind: 'retired', use: '/help' },
15
+ 'pty-view': { kind: 'retired', use: '/help' }, fullscreen: { kind: 'retired', use: '/help' },
16
+ fs: { kind: 'retired', use: '/help' }, view: { kind: 'retired', use: '/help' },
17
+ ctoggle: { kind: 'retired', use: '/help' }, cache: { kind: 'retired', use: '/help' },
18
+ perf: { kind: 'retired', use: '/help' }, 'pty-list': { kind: 'retired', use: '/help' },
19
+ ptys: { kind: 'retired', use: '/help' }, 'sweep-tool-results': { kind: 'retired', use: '/help' },
20
+ budget: { kind: 'retired', use: '/tokens' }, b: { kind: 'retired', use: '/tokens' },
21
+ route: { kind: 'retired', use: '/help' }, llm: { kind: 'retired', use: '/model' },
22
+ 'browser-cdp': { kind: 'retired', use: '/help' }, bcdp: { kind: 'retired', use: '/help' },
23
+ dm: { kind: 'retired', use: '/help' }, surf: { kind: 'retired', use: '/help' },
24
+ 'codex-vw': { kind: 'retired', use: '/help' }, 'skill-triggers': { kind: 'retired', use: '/help' },
25
+ triggers: { kind: 'retired', use: '/help' }, git: { kind: 'retired', use: '/help' },
26
+ memorize: { kind: 'retired', use: '/memory' }, 'mem-compact': { kind: 'retired', use: '/memory' },
27
+ sim: { kind: 'retired', use: '/help' }, simulator: { kind: 'retired', use: '/help' },
28
+ 'turn-slider': { kind: 'retired', use: '/help' }, turnslider: { kind: 'retired', use: '/help' },
29
+ tslider: { kind: 'retired', use: '/help' }, playground: { kind: 'retired', use: '/help' },
30
+ pg: { kind: 'retired', use: '/help' }, branch: { kind: 'retired', use: '/help' },
31
+ audit: { kind: 'retired', use: '/help' }, 'substrate-stats': { kind: 'retired', use: '/help' },
32
+ sst: { kind: 'retired', use: '/help' }, usage: { kind: 'retired', use: '/tokens' },
33
+ stats: { kind: 'retired', use: '/help' }, 'codex-setup': { kind: 'retired', use: '/help' },
34
+ 'codex-init': { kind: 'retired', use: '/help' }, hint: { kind: 'retired', use: '/help' },
35
+ media: { kind: 'retired', use: '/help' }, mv: { kind: 'retired', use: '/help' },
36
+ };
37
+
38
+ function distanceAtMostTwo(a: string, b: string): number {
39
+ if (Math.abs(a.length - b.length) > 2) return 3;
40
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
41
+ for (let i = 1; i <= a.length; i++) {
42
+ const current = [i];
43
+ for (let j = 1; j <= b.length; j++) {
44
+ current[j] = Math.min(
45
+ current[j - 1]! + 1,
46
+ previous[j]! + 1,
47
+ previous[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1),
48
+ );
49
+ }
50
+ if (Math.min(...current) > 2) return 3;
51
+ previous = current;
52
+ }
53
+ return previous[b.length]!;
54
+ }
55
+
56
+ /** Plain lines: the caller applies its existing warning/muted presentation. */
57
+ export function unknownSlashReply(name: string, registered: readonly string[]): string[] {
58
+ const slashName = name.replace(/^\//, '').toLowerCase();
59
+ const advice = Object.hasOwn(RETIRED_SLASH_ADVICE, slashName)
60
+ ? RETIRED_SLASH_ADVICE[slashName] : undefined;
61
+ if (advice?.kind === 'moved') {
62
+ return [`/${slashName} has moved; use ${advice.use}`];
63
+ }
64
+ if (advice) return [`/${slashName} is retired; use ${advice.use}`];
65
+
66
+ const suggestions = [...new Set(registered)]
67
+ .filter((candidate) => !Object.hasOwn(RETIRED_SLASH_ADVICE, candidate))
68
+ .map((candidate) => ({ candidate, distance: distanceAtMostTwo(slashName, candidate) }))
69
+ .filter(({ distance }) => distance <= 2)
70
+ .sort((a, b) => a.distance - b.distance || a.candidate.localeCompare(b.candidate))
71
+ .slice(0, 3)
72
+ .map(({ candidate }) => `/${candidate}`);
73
+ return [
74
+ `Unknown command: /${slashName}`,
75
+ ...(suggestions.length ? [`Did you mean: ${suggestions.join(', ')}?`] : []),
76
+ 'Type /help for the list of commands.',
77
+ ];
78
+ }
@@ -0,0 +1,77 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+
5
+ import { readInstallMetadataCommit } from '../version/code-revision.js';
6
+
7
+ export type StaleInstallResult = {
8
+ state: 'same' | 'changed' | 'unknown' | 'not-installed';
9
+ bootCommit: string | undefined;
10
+ installedCommit: string | undefined;
11
+ };
12
+
13
+ export const STALE_INSTALL_CHECK_INTERVAL_MS = 60_000;
14
+
15
+ /** Identify the immutable installed version containing this module, never the session cwd. */
16
+ export function versionedDashboardInstallDir(
17
+ codeRoot: string,
18
+ installRoot = join(homedir(), '.local', 'share', 'elanous'),
19
+ ): string | undefined {
20
+ try {
21
+ const packageRoot = realpathSync(codeRoot);
22
+ if (basename(packageRoot) !== 'elanous' || basename(dirname(packageRoot)) !== 'node_modules') return undefined;
23
+ const versionDir = dirname(dirname(packageRoot));
24
+ if (dirname(versionDir) !== resolve(installRoot, 'versions')) return undefined;
25
+ return versionDir;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+
31
+ export function createStaleInstallCheck(input: {
32
+ bootCommit: string | undefined;
33
+ readInstalledCommit: () => string | undefined;
34
+ now: () => number;
35
+ codeRoot?: string;
36
+ installRoot?: string;
37
+ }) {
38
+ const installed = input.codeRoot === undefined
39
+ || versionedDashboardInstallDir(input.codeRoot, input.installRoot) !== undefined;
40
+ const notifiedCommits = new Set<string>();
41
+ let lastReadAt = -Infinity;
42
+ let lastResult: StaleInstallResult | undefined;
43
+ const check = (): StaleInstallResult => {
44
+ if (!installed) return { state: 'not-installed', bootCommit: input.bootCommit, installedCommit: undefined };
45
+ const time = input.now();
46
+ if (lastResult && time - lastReadAt < STALE_INSTALL_CHECK_INTERVAL_MS) return lastResult;
47
+ lastReadAt = time;
48
+ let installedCommit: string | undefined;
49
+ try { installedCommit = input.readInstalledCommit(); } catch { /* unreadable metadata */ }
50
+ const result: StaleInstallResult = {
51
+ state: !input.bootCommit || !installedCommit
52
+ ? 'unknown'
53
+ : input.bootCommit === installedCommit ? 'same' : 'changed',
54
+ bootCommit: input.bootCommit,
55
+ installedCommit,
56
+ };
57
+ lastResult = result;
58
+ return result;
59
+ };
60
+ return {
61
+ check,
62
+ shouldNotify(result: StaleInstallResult): boolean {
63
+ if (result.state !== 'changed' || !result.installedCommit || notifiedCommits.has(result.installedCommit)) return false;
64
+ notifiedCommits.add(result.installedCommit);
65
+ return true;
66
+ },
67
+ };
68
+ }
69
+
70
+ /** Only the current pointer is polled; boot metadata is read once from the immutable version. */
71
+ export function readDashboardBootCommit(versionDir: string): string | undefined {
72
+ return readInstallMetadataCommit(join(versionDir, 'install.json'));
73
+ }
74
+
75
+ export function readCurrentInstalledCommit(installRoot = join(homedir(), '.local', 'share', 'elanous')): string | undefined {
76
+ return readInstallMetadataCommit(join(installRoot, 'current', 'install.json'));
77
+ }