elanous 0.2.4 → 0.2.5

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 (412) hide show
  1. package/README.md +3 -3
  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/06ef2eba-d1b99ac6d2ea663c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
  12. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
  43. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_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 +8 -1
  172. package/graphs/release/release-loop.yaml +6 -1
  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/job-coach/.codex-plugin/plugin.json +18 -1
  177. package/plugins/job-coach/README.md +12 -0
  178. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  179. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  180. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  181. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  182. package/plugins/job-coach/graphs/run-step.ts +211 -17
  183. package/plugins/job-coach/plugin.json +16 -3
  184. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  185. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  186. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  187. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  188. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  189. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  190. package/scripts/backup/elanous-backup.sh +4 -1
  191. package/scripts/build-docs-site.ts +4 -3
  192. package/scripts/coord-channel-watch.sh +23 -4
  193. package/scripts/coord-post.sh +49 -12
  194. package/scripts/coord-tracks.json +5 -5
  195. package/scripts/eval-bot-vs-grok.ts +79 -0
  196. package/scripts/eval-grok-live-baseline.ts +36 -0
  197. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  198. package/scripts/fleet/node-setup.sh +46 -7
  199. package/scripts/landing-heal/must-fix-match.ts +26 -0
  200. package/scripts/landing-heal/run-mission.ts +114 -20
  201. package/scripts/lib/front-route-measurement.ts +131 -0
  202. package/scripts/lib/tui-sim-keys.ts +42 -0
  203. package/scripts/measure-front-route-classifier.ts +56 -0
  204. package/scripts/media/ocr-leak-scan.sh +21 -0
  205. package/scripts/public-export.ts +2 -3
  206. package/scripts/public-import-pr.ts +80 -6
  207. package/scripts/publish-internal-dist.ts +2 -3
  208. package/scripts/red-sweep-diff.ts +1 -1
  209. package/scripts/release-loop/auto-approve-node.ts +72 -0
  210. package/scripts/release-loop/gate-node.ts +332 -44
  211. package/scripts/release-loop/publish-node.ts +7 -3
  212. package/scripts/release-loop/release-docs.ts +20 -13
  213. package/scripts/release-loop/test-durations.ts +63 -0
  214. package/scripts/release-loop/upgrade-node.ts +3 -2
  215. package/scripts/test-audit-coverage-per-file.sh +16 -0
  216. package/scripts/test-audit-meaningless.ts +88 -0
  217. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  218. package/scripts/test-deterministic.ts +27 -5
  219. package/scripts/tui-sim.ts +59 -14
  220. package/scripts/ux/bump-installed.sh +20 -0
  221. package/scripts/ux/lib/cdp.ts +72 -0
  222. package/scripts/ux/record-page.ts +50 -0
  223. package/skills/explainer-video/SKILL.md +99 -0
  224. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  225. package/skills/explainer-video/engine/build.mjs +431 -0
  226. package/skills/explainer-video/engine/vo.ts +37 -0
  227. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  228. package/src/agent/nested-agent-env.ts +1 -0
  229. package/src/agent-mission/driver.ts +95 -16
  230. package/src/agent-mission/mission-cli.ts +6 -0
  231. package/src/agent-mission/resource-ladder.ts +73 -25
  232. package/src/auth/acp-token.ts +16 -0
  233. package/src/auth/github-app-token.ts +112 -0
  234. package/src/autopilot/gh-rest-fallback.ts +177 -0
  235. package/src/autopilot/pr-manager.ts +51 -4
  236. package/src/boot/acp-server.ts +2 -16
  237. package/src/boot/daemon-tools/web-search.ts +2 -1
  238. package/src/boot/ws-bridge.ts +11 -0
  239. package/src/chat/index.ts +37 -8
  240. package/src/chat/log-scroll-key.ts +12 -0
  241. package/src/chat/submit-trace.ts +30 -0
  242. package/src/cli/agent-cli.ts +7 -2
  243. package/src/cli/decisions-cli.ts +94 -0
  244. package/src/cli/doctor-cli.ts +40 -54
  245. package/src/cli/doctor-readiness.ts +16 -24
  246. package/src/cli/intake-cli.ts +1 -1
  247. package/src/cli/mcp-call.ts +9 -0
  248. package/src/cli/mcp-gateway-cli.ts +7 -3
  249. package/src/cli/plugin-cli.ts +109 -11
  250. package/src/cli/pr-lineage.ts +2 -1
  251. package/src/cli/pwa-start.ts +3 -0
  252. package/src/cli/release-cli.ts +94 -7
  253. package/src/cli/self-update.ts +7 -4
  254. package/src/cli/start-cli.ts +33 -14
  255. package/src/dashboard/acp-boot.ts +3 -0
  256. package/src/dashboard/index.ts +62 -13
  257. package/src/dashboard/input/no-provider-reply.ts +13 -0
  258. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  259. package/src/dashboard/stale-install-check.ts +77 -0
  260. package/src/decisions/decision-ledger.ts +404 -0
  261. package/src/domains/outbound-alert.ts +8 -2
  262. package/src/domains/telegram-kind-route.ts +21 -0
  263. package/src/execution-loop/dispatch-task.ts +139 -0
  264. package/src/execution-loop/gate-decision-summary.ts +29 -0
  265. package/src/execution-loop/gates.ts +197 -0
  266. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  267. package/src/graph-kinds/plugin-nodes.ts +10 -2
  268. package/src/graph-runner/runner.ts +35 -5
  269. package/src/grounding/token.ts +12 -6
  270. package/src/harness/harness-cli-command.ts +68 -9
  271. package/src/harness/harness-pod-dispatch.ts +8 -2
  272. package/src/harness/harness-stop.ts +147 -18
  273. package/src/harness/self-send-decide.ts +26 -17
  274. package/src/harness/self-send-target.ts +22 -1
  275. package/src/harness/worktree-branch-prefix.ts +4 -3
  276. package/src/index.ts +56 -13
  277. package/src/instance/resolve.ts +6 -0
  278. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  279. package/src/intake-plane/digest.ts +11 -2
  280. package/src/live/detail-switch.ts +8 -3
  281. package/src/llm/usable-llm.ts +32 -0
  282. package/src/loops/registry.ts +27 -5
  283. package/src/market/publish.ts +59 -3
  284. package/src/native-tool-catalog.ts +2 -2
  285. package/src/nexus/api/graph-approvals.ts +125 -0
  286. package/src/nexus/api/http-server.ts +60 -6
  287. package/src/nexus/api/log-fabric.ts +10 -1
  288. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  289. package/src/nexus/api/merge-approvals.ts +2 -1
  290. package/src/nexus/api/meta-api.ts +14 -0
  291. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  292. package/src/nexus/api/plugins-market.ts +87 -3
  293. package/src/nexus/api/pod-credential-api.ts +68 -14
  294. package/src/nexus/api/public-routes.ts +7 -0
  295. package/src/nexus/api/trace.ts +57 -6
  296. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  297. package/src/nexus/index.ts +33 -32
  298. package/src/nexus/outbound/router.ts +1 -1
  299. package/src/onboarding.ts +25 -2
  300. package/src/plugins/core/host.ts +14 -0
  301. package/src/plugins/install/market-fetch.ts +4 -3
  302. package/src/plugins/install/plugin-credentials.ts +106 -0
  303. package/src/plugins/install/plugin-install.ts +163 -32
  304. package/src/prompt-library/universal-preamble.ts +8 -2
  305. package/src/release-loop/checklist.ts +191 -0
  306. package/src/release-loop/release-note.ts +6 -3
  307. package/src/self-dev/dev-cli.ts +40 -20
  308. package/src/self-dev/dev-pipeline.ts +77 -1
  309. package/src/self-implement/gate-baseline.ts +3 -1
  310. package/src/self-implement/goal-author.ts +18 -1
  311. package/src/self-implement/host-regate.ts +6 -2
  312. package/src/self-implement/orchestrator.ts +7 -2
  313. package/src/self-implement/seams.ts +4 -2
  314. package/src/skills/tools/dev-harness.ts +42 -0
  315. package/src/steward/steward-cards.ts +45 -0
  316. package/src/steward/triage.ts +50 -14
  317. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  318. package/src/task-orchestrator/surfaces/pod-command-job.ts +27 -6
  319. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  320. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  321. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  322. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  323. package/src/telegram-report.ts +12 -6
  324. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  325. package/src/tui.ts +3 -2
  326. package/src/user-config.ts +28 -2
  327. package/src/util/spawn-sync-output.ts +15 -0
  328. package/src/version/code-revision.ts +1 -1
  329. package/src/version/packed-revision.json +1 -1
  330. package/src/web-search/free.ts +77 -0
  331. package/src/web-search/index.ts +2 -0
  332. package/src/workflow-runtime/deps-bridge.ts +4 -3
  333. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  334. package/src/workflow-runtime/types.ts +2 -1
  335. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  346. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  348. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/8142-011e1f474efb8b33.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  411. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  412. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
@@ -28,13 +28,21 @@
28
28
  // daemon exit.
29
29
 
30
30
  import type { McpServerSpec } from '../../user-config.js';
31
- import { McpClient, McpConnectionError } from '../../mcp/client.js';
31
+ import { McpClient, McpConnectionError, type McpToolCallResult } from '../../mcp/client.js';
32
32
  import {
33
33
  createMcpProxyRuntime,
34
34
  createMcpToolAuthorizer,
35
35
  } from '../../mcp/proxy-runtime.js';
36
36
  import { registerToolRuntime, unregisterToolRuntime } from '../../tool-runtime/registry.js';
37
37
  import { debug, redactSecretText } from '../../debug/log.js';
38
+ import { elanousStateRoot } from '../../autopilot/state-paths.js';
39
+ import { spawn as nodeSpawn } from 'node:child_process';
40
+ import { listNodeKinds } from '../../graph-kinds/registry.js';
41
+ import { syncInstalledPluginNodes } from '../../graph-kinds/installed-plugin-nodes.js';
42
+ import { listInstalledPlugins } from '../../plugins/install/plugin-install.js';
43
+ import { pluginEnv, credentialStatus } from '../../plugins/install/plugin-credentials.js';
44
+ import { existsSync, realpathSync } from 'node:fs';
45
+ import { resolve, sep } from 'node:path';
38
46
 
39
47
  export interface McpClientsHandle {
40
48
  /** Live clients (one per successfully started server). */
@@ -172,24 +180,64 @@ export async function registerMcpClients(
172
180
  // breadcrumbs when a child (e.g. xcrun mcpbridge) deadlocked elanous's
173
181
  // boot. The trace category prefix `mcp.client.boot.*` keeps these
174
182
  // distinct from any per-call instrumentation a future PR adds.
183
+ const injectedValuesByServer = new Map<string, string[]>();
184
+ const redactPluginValues = (text: string): string => {
185
+ try {
186
+ const root = elanousStateRoot();
187
+ const current = [...new Set(listInstalledPlugins(root).map(item => item.name))]
188
+ .flatMap(name => Object.values(pluginEnv(name, root)));
189
+ return redactValues(text, [...current, ...injectedValuesByServer.values()].flat());
190
+ } catch { return '[REDACTED]'; }
191
+ };
175
192
  const bootLogger = (id: string) => (event: string, data?: Record<string, unknown>) => {
176
193
  try {
177
- debug.log('mcp.client.boot', event, { id, ...(data ?? {}) });
194
+ const safe = data ? Object.fromEntries(Object.entries(data).map(([key, value]) =>
195
+ [key, typeof value === 'string' ? redactPluginValues(value) : value])) : {};
196
+ debug.log('mcp.client.boot', event, { id, ...safe });
178
197
  } catch { /* logger must never throw the boot */ }
179
198
  };
180
199
  const createClient =
181
200
  opts.createClient ??
182
- ((spec: McpServerSpec) =>
183
- spec.transport === 'http'
184
- ? new McpClient({
185
- id: spec.id,
186
- url: spec.url,
187
- logger: bootLogger(spec.id),
188
- ...(spec.oauthIssuer ? { oauthIssuer: spec.oauthIssuer } : {}),
189
- ...(spec.oauthTokenEndpoint ? { oauthTokenEndpoint: spec.oauthTokenEndpoint } : {}),
190
- ...(spec.bearerTokenEnv ? { bearerTokenEnv: spec.bearerTokenEnv } : {}),
191
- })
192
- : new McpClient({ id: spec.id, command: spec.command, logger: bootLogger(spec.id) }));
201
+ ((spec: McpServerSpec) => {
202
+ if (spec.transport === 'http') return new McpClient({
203
+ id: spec.id, url: spec.url, logger: bootLogger(spec.id),
204
+ ...(spec.oauthIssuer ? { oauthIssuer: spec.oauthIssuer } : {}),
205
+ ...(spec.oauthTokenEndpoint ? { oauthTokenEndpoint: spec.oauthTokenEndpoint } : {}),
206
+ ...(spec.bearerTokenEnv ? { bearerTokenEnv: spec.bearerTokenEnv } : {}),
207
+ });
208
+ const root = elanousStateRoot();
209
+ const installed = listInstalledPlugins(root);
210
+ const owners = [...new Set(listNodeKinds('workflow').filter(entry =>
211
+ !entry.core && entry.plugin && entry.run && 'mcp' in entry.run && entry.run.mcp.server === spec.id,
212
+ ).map(entry => entry.plugin!))];
213
+ const commandOwners = [...new Set(installed.filter(item => {
214
+ const dir = realpathSync(item.path);
215
+ return spec.command.some(arg => {
216
+ if (typeof arg !== 'string' || !existsSync(arg)) return false;
217
+ try { return realpathSync(resolve(arg)).startsWith(`${dir}${sep}`); }
218
+ catch { return false; }
219
+ });
220
+ }).map(item => item.name))];
221
+ const selected = commandOwners.length === 1 && owners.length === 1 && owners[0] === commandOwners[0]
222
+ ? commandOwners[0] : undefined;
223
+ return new McpClient({ id: spec.id, command: spec.command, logger: bootLogger(spec.id),
224
+ spawn: (cmd, args) => {
225
+ const declaredEnv = [...new Set(listInstalledPlugins(root).map(item => item.name))]
226
+ .flatMap(name => credentialStatus(name, root).fields.map(field => field.env));
227
+ const childEnv = { ...process.env };
228
+ for (const key of declaredEnv) delete childEnv[key];
229
+ injectedValuesByServer.delete(spec.id);
230
+ if (selected) {
231
+ const env = pluginEnv(selected, root);
232
+ const fields = credentialStatus(selected, root).fields.filter(field => field.set).map(field => field.name);
233
+ debug.log('plugin.credentials', 'injected', { plugin: selected, fields, count: fields.length });
234
+ Object.assign(childEnv, env);
235
+ injectedValuesByServer.set(spec.id, Object.values(env));
236
+ }
237
+ return nodeSpawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'], env: childEnv });
238
+ },
239
+ });
240
+ });
193
241
  const handshakeTimeoutMs = opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
194
242
  const setTimeoutFn = opts.setTimeoutFn
195
243
  ?? ((cb: () => void, ms: number) => {
@@ -204,6 +252,11 @@ export async function registerMcpClients(
204
252
  const perServer: Record<string, McpServerBootResult> = {};
205
253
  let registered = 0;
206
254
 
255
+ if (opts.servers.some(spec => spec.transport === 'stdio') && !opts.createClient) {
256
+ try { syncInstalledPluginNodes(elanousStateRoot()); }
257
+ catch (error) { debug.log('graph.kinds', 'installed-sync-failed', { reason: String(error) }); }
258
+ }
259
+
207
260
  for (const spec of opts.servers) {
208
261
  const serverHandshakeTimeoutMs = spec.handshakeTimeoutMs ?? handshakeTimeoutMs;
209
262
  if (spec.enabled === false) {
@@ -213,6 +266,20 @@ export async function registerMcpClients(
213
266
  let client: McpClient | undefined;
214
267
  try {
215
268
  client = createClient(spec) as McpClient;
269
+ if (!opts.createClient && spec.transport === 'stdio') {
270
+ const originalCallTool = client.callTool.bind(client);
271
+ client.callTool = async (name: string, args: Record<string, unknown>): Promise<McpToolCallResult> => {
272
+ const values = injectedValuesByServer.get(spec.id) ?? [];
273
+ try {
274
+ const result = await originalCallTool(name, args);
275
+ return maskInjectedValues(result, values) as McpToolCallResult;
276
+ } catch (err) {
277
+ if (err instanceof Error) err.message = redactValues(err.message, values);
278
+ else throw new Error(redactValues(String(err), values));
279
+ throw err;
280
+ }
281
+ };
282
+ }
216
283
  await withTimeout(`mcp.${spec.id}.start`, () => client!.start(), serverHandshakeTimeoutMs, setTimeoutFn, clearTimeoutFn);
217
284
  const tools = await withTimeout(
218
285
  `mcp.${spec.id}.listTools`,
@@ -287,7 +354,7 @@ export async function registerMcpClients(
287
354
  }
288
355
  const msg = err instanceof Error ? err.message : String(err);
289
356
  const reasonClass = classifyMcpConnectFailure(err);
290
- const detail = redactSecretText(msg);
357
+ const detail = redactPluginValues(redactSecretText(msg));
291
358
  perServer[spec.id] = { status: 'failed', toolCount: 0, reason: detail, reasonClass };
292
359
  try {
293
360
  debug.log('mcp.client.boot', 'connect-failed', { id: spec.id, reason: reasonClass, detail });
@@ -303,6 +370,7 @@ export async function registerMcpClients(
303
370
  if (client) {
304
371
  try { await client.dispose(); } catch { /* best-effort */ }
305
372
  }
373
+ injectedValuesByServer.delete(spec.id);
306
374
  }
307
375
  }
308
376
 
@@ -328,6 +396,7 @@ export async function registerMcpClients(
328
396
  }),
329
397
  ),
330
398
  );
399
+ injectedValuesByServer.clear();
331
400
  },
332
401
  };
333
402
  }
@@ -338,3 +407,19 @@ function defaultLogger(): McpBootLogger {
338
407
  warn: (line) => console.warn(line),
339
408
  };
340
409
  }
410
+
411
+ function redactValues(text: string, values: readonly string[]): string {
412
+ return values.filter(value => value.length > 0)
413
+ .reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
414
+ }
415
+
416
+ /** Mask injected credential values anywhere in an MCP result — string values **and object keys**
417
+ * (a server can echo a secret back as a key, e.g. `structuredContent: { [secret]: "ok" }`). */
418
+ export function maskInjectedValues(value: unknown, values: readonly string[]): unknown {
419
+ if (typeof value === 'string') return redactValues(value, values);
420
+ if (Array.isArray(value)) return value.map(item => maskInjectedValues(item, values));
421
+ if (value && typeof value === 'object') {
422
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [redactValues(key, values), maskInjectedValues(item, values)]));
423
+ }
424
+ return value;
425
+ }
@@ -79,6 +79,8 @@ import { type PtyBackend, createWebtermSpawn, resolveWebtermCwd } from './webter
79
79
  import { SettingsTabController } from './config/settings-controller.js';
80
80
  import { tryRegisterSettings } from './boot/register-settings.js';
81
81
  import { registerMcpClients, type McpClientsHandle } from './boot/register-mcp-clients.js';
82
+ import { syncInstalledPluginNodes } from '../graph-kinds/installed-plugin-nodes.js';
83
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
82
84
  import type { McpReloadOutcome, McpReloadServerResult } from './api/admin-mcp-reload.js';
83
85
  import { loadOrCreateSigningKey, NonceStore } from './api/edit-in-pwa-core.js';
84
86
  import { subscribeErrorSnapshotWriter } from './supervisor/error-snapshot.js';
@@ -238,9 +240,8 @@ import {
238
240
  type PwaVoiceAdapter,
239
241
  } from '../voice/channel-adapters/pwa-voice-adapter.js';
240
242
  import { getIntakeStore } from '../intake-plane/runtime.js';
241
- import { existsSync, readFileSync } from 'node:fs';
242
243
  import { join as joinPath } from 'node:path';
243
- import { getElanousConfigDir } from '../elanous-config-dir.js';
244
+ import { ensureAuthToken } from '../auth/acp-token.js';
244
245
  import type { SupervisorSpawnBackend } from './supervisor/spawn.js';
245
246
  import type { HealthProbeBackend } from './supervisor/health.js';
246
247
  import { gracefulExit, cleanExit } from './supervisor/graceful-exit.js';
@@ -264,8 +265,10 @@ import { createGlobalSubagentCallable } from '../agent/subagent-callable.js';
264
265
  import { setAgentHopCap } from '../agent/registry.js';
265
266
  import type { SidebarTabSurface } from '../ui/widgets/sidebar-tab-surface.js';
266
267
  import { defaultIO, runOnboardingStep, type WizardIO } from '../onboarding.js';
268
+ import { packageVersion } from '../version/code-revision.js';
267
269
 
268
- export const NEXUS_VERSION = '0.17.0';
270
+ // 데몬 배너·`nexus status`·연결 라벨이 보이는 판 = 제품 판(`package.json`) — 옛 내부 상수 `0.17.0` 이 0.2.x 제품 옆에 떴다(🅞 M5 · M5c).
271
+ export const NEXUS_VERSION = packageVersion();
269
272
  export const NEXUS_PHASE = 'N-5 PR χ (graceful exit + restart pending)';
270
273
 
271
274
  type TelegramAgentRunTurnFactory = typeof import('../telegram-agent.js').makeTelegramAgentRunTurn;
@@ -397,6 +400,8 @@ export interface RunNexusOptions {
397
400
  * registry stays empty so the dispatcher's webterm interception
398
401
  * is a no-op). */
399
402
  webtermSpawn?: (spec: { id: string; cwd?: string }) => PtyBackend;
403
+ /** Explicitly disable HTTP and ACP websocket token auth for this run. */
404
+ noAuth?: boolean;
400
405
  /** Skip the HTTP API server boot. Default: true in tests via
401
406
  * detachForTesting; production runs always start the server. Tests
402
407
  * that DO want the server can pass false explicitly. */
@@ -653,7 +658,7 @@ export interface RunNexusHandle {
653
658
  /** ACP WS auth gate for the NEXUS HTTP bridge.
654
659
  * Same shape as `src/boot/acp-server.ts` websocket auth: pass
655
660
  * `createAuthVerifier` only when a bearer token is configured;
656
- * omit the field entirely for first-boot tokenless dogfood.
661
+ * explicit no-auth opt-out omits the verifier.
657
662
  * ⛔ `noAuth` was declared on WsBridgeOpts but the bridge NEVER read it
658
663
  * (0 executable references) while this file assigned it — a value that
659
664
  * flowed in and died. We removed the field rather than leave a knob that
@@ -710,7 +715,7 @@ export function buildNexusWsBridgeAuth(
710
715
  bearerToken: string | undefined,
711
716
  paths?: TokenStorePaths,
712
717
  ): Pick<NexusWsBridgeInit, 'wsAuthVerifier'> {
713
- // ⛔ 첫 부팅 무인증 도그푸드는 그대로다 — 부팅 토큰이 «없으면» 검증기를 안 넘긴다.
718
+ // Explicit opt-out supplies no boot token, so the bridge has no verifier.
714
719
  if (!bearerToken) return {};
715
720
  return {
716
721
  // ⭐ 배열이 아니라 «공급자» — 매 핸드셰이크마다 봉투를 다시 읽는다.
@@ -764,6 +769,13 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
764
769
  }
765
770
 
766
771
  const leasePortFromLauncher = checkLauncherLeaseBoot(opts, process.env, () => resolveCurrentInstance().kind);
772
+ const noAuth = opts.noAuth === true || process.env.ELANOUS_NEXUS_NO_AUTH === '1';
773
+ // Resolve before acquiring the daemon lock or binding HTTP. A broken token
774
+ // store must fail closed without leaving a half-booted Nexus behind.
775
+ const httpBootEnabled = !(opts.skipHttpServer ?? !!opts.detachForTesting);
776
+ const needsToken = httpBootEnabled && !(opts.skipRuntimeApi ?? !!opts.detachForTesting) && !noAuth;
777
+ const bearerToken = needsToken ? ensureAuthToken().token : undefined;
778
+ if (needsToken && !bearerToken) throw new Error('Nexus auth token is empty');
767
779
 
768
780
  // 설치본 전환 RFC 0b — 키는 launchd plist 가 아니라 키 캐시에서(캐시 우선 · 자식도 물려받는다). 값은 안 찍고 «이름만».
769
781
  if (!opts.detachForTesting) {
@@ -973,6 +985,11 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
973
985
  });
974
986
  const startLevel = resolvedLevel.level;
975
987
  dbgMod.debug.setLevel(startLevel);
988
+ if (httpBootEnabled && noAuth) {
989
+ dbgMod.debug.log('nexus.auth', 'explicit-opt-out', {
990
+ source: opts.noAuth === true ? '--no-auth' : 'ELANOUS_NEXUS_NO_AUTH',
991
+ }, { level: 'warn' });
992
+ }
976
993
  dbgMod.debug.log('logging.level', 'startup-resolved', {
977
994
  level: startLevel, source: resolvedLevel.source,
978
995
  gateOpen: scopedMod.hotPathGateOpen(startLevel), surface: 'nexus',
@@ -1340,6 +1357,10 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
1340
1357
  current: () => false,
1341
1358
  };
1342
1359
 
1360
+ // Installed graph kinds do not depend on the runtime API or MCP server configuration.
1361
+ try { syncInstalledPluginNodes(elanousStateRoot()); }
1362
+ catch (error) { debug.log('graph.kinds', 'installed-sync-failed', { reason: String(error) }); }
1363
+
1343
1364
  if (!skipRuntime) {
1344
1365
  // Tool surface — flag · env · UserConfig switch · switch default.
1345
1366
  // See resolveToolsKind() jsdoc for resolution order.
@@ -2000,11 +2021,6 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
2000
2021
  let metaApiOpts: import('./api/meta-api.js').MetaApiOpts | undefined;
2001
2022
 
2002
2023
  if (!skipRuntime && runtimeHistory) {
2003
- // Bearer token — read from getElanousConfigDir()/acp-token (the
2004
- // canonical store `elanous serve` uses). Loopback default is
2005
- // noAuth; tests opt in by writing a token file before boot.
2006
- const bearerToken = readAcpToken();
2007
-
2008
2024
  // Voice REST + adapter. createVoiceRestHandler is stateless;
2009
2025
  // the STT provider singleton (getDaemonSttProvider) returns
2010
2026
  // null until `initDaemonSttProvider()` is called — fine, the
@@ -2081,9 +2097,8 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
2081
2097
  const surfaceForOpts = toolSurface(toolsKind);
2082
2098
  const toolCwdForOpts = resolveToolCwd({ tools: toolsKind, toolCwd: opts.toolCwd });
2083
2099
 
2084
- // Same shape as src/boot/acp-server.ts websocket auth: pass
2085
- // createAuthVerifier only when a token is present; omit it
2086
- // entirely for first-boot dogfood (tokenless ACP upgrade).
2100
+ // ACP websocket and REST share the same boot token; explicit
2101
+ // opt-out omits the websocket verifier as well.
2087
2102
  wsBridgeOpts = {
2088
2103
  ...(acpHandlerRef ? {
2089
2104
  acpOnConnection: async (conn) => {
@@ -2099,7 +2114,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
2099
2114
  metaApiOpts = {
2100
2115
  voiceRest,
2101
2116
  ...(bearerToken ? { bearerToken } : {}),
2102
- noAuth: !bearerToken,
2117
+ noAuth,
2103
2118
  history: runtimeHistory,
2104
2119
  intakeStore: getIntakeStore(),
2105
2120
  toolSurface: surfaceForOpts,
@@ -3048,7 +3063,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3048
3063
  }
3049
3064
  runtime.httpPort = httpServer.port;
3050
3065
  runtime.httpHost = httpServer.hostname;
3051
- runtime.httpAuth = 'off';
3066
+ runtime.httpAuth = metaApiOpts && !metaApiOpts.noAuth ? 'on' : 'off';
3052
3067
  // The actual bound port (not the requested port or registry entry) is the member endpoint.
3053
3068
  try {
3054
3069
  const configured = (getUserConfig().raw?.nexus as { primary?: unknown } | undefined)?.primary;
@@ -3475,6 +3490,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3475
3490
  }
3476
3491
 
3477
3492
  printBootBanner(runtime, pwaStaticDir);
3493
+ if (runtime.httpPort && noAuth) {
3494
+ console.warn(' ⚠ NEXUS AUTH DISABLED (--no-auth / ELANOUS_NEXUS_NO_AUTH=1) — HTTP API and ACP websocket are unprotected.');
3495
+ }
3478
3496
 
3479
3497
  // P.3 — first-boot PWA share wizard. Only runs in interactive TUI mode
3480
3498
  // when the switch is still 'ask'. Headless / non-TTY boots skip it (the
@@ -3784,23 +3802,6 @@ function shouldAskPwaShareSwitch(): boolean {
3784
3802
  }
3785
3803
  }
3786
3804
 
3787
- /** PR k — load the persistent ACP bearer token at
3788
- * `getElanousConfigDir()/acp-token`. Mirrors the helper in
3789
- * `boot/acp-server.ts` that `elanous serve` uses, so the same token
3790
- * gates daemon and NEXUS HTTP. Returns undefined when the token
3791
- * file is absent (loopback noAuth default — first-boot dogfood
3792
- * works without configuration). Read failures are swallowed. */
3793
- export function readAcpToken(): string | undefined {
3794
- try {
3795
- const tokenPath = joinPath(getElanousConfigDir(), 'acp-token');
3796
- if (existsSync(tokenPath)) {
3797
- const tok = readFileSync(tokenPath, 'utf-8').trim();
3798
- return tok || undefined;
3799
- }
3800
- } catch { /* swallow */ }
3801
- return undefined;
3802
- }
3803
-
3804
3805
  function printBootBanner(runtime: NexusRuntimeMeta, pwaStaticDir?: string): void {
3805
3806
  const lines = [
3806
3807
  '',
@@ -141,7 +141,7 @@ export function resolveChannels(
141
141
  async function deliverTelegram(cfg: UserConfig, msg: OutboundMsg, deps: RouterDeps): Promise<ChannelResult> {
142
142
  const fmt = formatForChannel('telegram', msg);
143
143
  const send = deps.telegramSend ?? sendTelegramReport;
144
- const ok = await send(cfg, fmt.text, { markdown: fmt.markdown ?? msg.markdown, fetchImpl: deps.fetchImpl });
144
+ const ok = await send(cfg, fmt.text, { markdown: fmt.markdown ?? msg.markdown, fetchImpl: deps.fetchImpl, kind: msg.kind });
145
145
  return ok ? { type: 'telegram', ok: true } : { type: 'telegram', ok: false, error: 'not-configured' };
146
146
  }
147
147
 
package/src/onboarding.ts CHANGED
@@ -1193,6 +1193,27 @@ export function shouldRefuseInteractiveOnboarding(
1193
1193
  return o.ctx !== 'production' || !o.stdinIsTTY;
1194
1194
  }
1195
1195
 
1196
+ export class OnboardingRefusedError extends Error {
1197
+ readonly code = 'onboarding-refused' as const;
1198
+
1199
+ constructor(message: string, readonly reason: 'non-tty' | 'autonomous-empty-universe') {
1200
+ super(message);
1201
+ this.name = 'OnboardingRefusedError';
1202
+ }
1203
+ }
1204
+
1205
+ /** Handle only typed refusals; the agent setup hint applies to its non-TTY path alone. */
1206
+ export function handleOnboardingRefusal(error: unknown, entrance: 'agent' | 'other'): boolean {
1207
+ if (!(error instanceof OnboardingRefusedError) || error.code !== 'onboarding-refused') return false;
1208
+ if (entrance === 'agent' && error.reason === 'non-tty') {
1209
+ console.error('elanous agent needs a configured LLM. Run `elanous setup` in a terminal, or `elanous setup --non-interactive --config <path>` for unattended setup.');
1210
+ } else {
1211
+ console.error(error.message);
1212
+ }
1213
+ process.exitCode = 2;
1214
+ return true;
1215
+ }
1216
+
1196
1217
  function refuseInteractiveOnboardingIfNeeded(opts: RunWizardOpts, path: string): void {
1197
1218
  const ctx = getRunContext();
1198
1219
  const stdinIsTTY = !!input.isTTY;
@@ -1208,15 +1229,17 @@ function refuseInteractiveOnboardingIfNeeded(opts: RunWizardOpts, path: string):
1208
1229
  : '자율 컨텍스트에서 대화형 온보딩 요청 — 우주가 비어 있다(물질화 누락)',
1209
1230
  }, { level: 'error' });
1210
1231
  if (nonTTY) {
1211
- throw new Error(
1232
+ throw new OnboardingRefusedError(
1212
1233
  '대화형 온보딩은 stdin TTY가 있는 자리에서만 실행할 수 있다. '
1213
1234
  + '무인 설정은 `elanous setup --non-interactive --config <path>`를 사용하라.',
1235
+ 'non-tty',
1214
1236
  );
1215
1237
  }
1216
- throw new Error(
1238
+ throw new OnboardingRefusedError(
1217
1239
  `온보딩 마법사는 자율 컨텍스트(${ctx})에서 뜰 수 없다 — config 가 비어 있다(${path}). `
1218
1240
  + '자식 우주가 물질화되지 않았다는 뜻이다: `elanous config sync-test --state-dir <그 우주>` '
1219
1241
  + '로 깔거나, 스포너가 provisionDerivedUniverse 를 부르는지 확인하라.',
1242
+ 'autonomous-empty-universe',
1220
1243
  );
1221
1244
  }
1222
1245
 
@@ -233,6 +233,14 @@ export const BUILTIN_DIR = resolve(import.meta.dir, '..', '..', '..', 'plugins')
233
233
  export const PACKS_DIR = resolve(import.meta.dir, '..', '..', '..', 'packs');
234
234
  export const USER_DIR = join(homedir(), '.claude', 'plugins');
235
235
 
236
+ /** 코드 없이 스킬·그래프만 싣는 팩인가 — 진입점이 기본값(`./plugin.ts`)이고 파일이 없으며, 스킬·그래프를 선언하거나 `skills/` 폴더가 있다. */
237
+ export function isSkillOnlyPack(pluginDir: string, manifest: { main: string; contributes?: { skills?: unknown[]; graphs?: unknown[] } }, hasLegacyEntry: boolean): boolean {
238
+ if (hasLegacyEntry) return false;
239
+ if (manifest.main !== './plugin.ts') return false; // 명시한 진입점이 없으면 그건 결함이다 — 경고를 유지한다.
240
+ const declares = (manifest.contributes?.skills?.length ?? 0) > 0 || (manifest.contributes?.graphs?.length ?? 0) > 0;
241
+ return declares || existsSync(join(pluginDir, 'skills'));
242
+ }
243
+
236
244
  export class PluginHost {
237
245
  private available = new Map<string, PluginEntry>();
238
246
  private discoveredNodeKinds = new Map<string, NodeKindEntry>();
@@ -614,6 +622,12 @@ export class PluginHost {
614
622
  // ~/.claude/plugins/{cache,data,marketplaces} internal state)
615
623
  // are silently skipped: they never intended to be plugins.
616
624
  const looksLikePlugin = hasLegacyEntry || !manifestLoad.inferred;
625
+ // 스킬·그래프만 싣는 팩(공식 `elanous-basics` 등)은 코드 진입점이 없다 — 매니페스트가 `main` 을
626
+ // 적지 않아 기본값 `./plugin.ts` 가 들어온 것뿐이다. 부팅마다 «main not found» 경고를 내지 않는다.
627
+ if (looksLikePlugin && isSkillOnlyPack(pluginDir, manifestLoad.manifest, hasLegacyEntry)) {
628
+ debug.log('plugin.discovery', 'skill-only', { plugin: manifestLoad.manifest.id });
629
+ continue;
630
+ }
617
631
  if (looksLikePlugin) {
618
632
  this.hooks.log(`⚠ plugin "${manifestLoad.manifest.id}" main not found: ${manifestLoad.manifest.main}`);
619
633
  debug.log('plugin.discovery', 'error', {
@@ -186,9 +186,10 @@ async function fetchMarketIndex(name: string, opts: MarketFetchOptions): Promise
186
186
  const load = async (file: string): Promise<Uint8Array> => {
187
187
  const url = new URL(file, market.url);
188
188
  let response: Response;
189
- try { response = await fetcher(url.href); }
190
- catch (error) { throw new MarketFetchError('io', `market fetch failed (${file}): ${error instanceof Error ? error.message : String(error)}`); }
189
+ try { response = await fetcher(url.href, { redirect: 'error' }); }
190
+ catch { throw new MarketFetchError('io', `market fetch failed (${file}): network error`); }
191
191
  if (!response.ok) throw new MarketFetchError('io', `market fetch failed (${file}): HTTP ${response.status}`);
192
+ if (response.redirected || (response.url && response.url !== url.href)) throw new MarketFetchError('io', `market fetch was redirected (${file})`);
192
193
  if (!response.body) throw new MarketFetchError('io', `market fetch failed (${file}): missing response body`);
193
194
  const reader = response.body.getReader();
194
195
  const chunks: Uint8Array[] = [];
@@ -203,7 +204,7 @@ async function fetchMarketIndex(name: string, opts: MarketFetchOptions): Promise
203
204
  }
204
205
  } catch (error) {
205
206
  if (error instanceof MarketFetchError) throw error;
206
- throw new MarketFetchError('io', `market fetch failed (${file}): ${error instanceof Error ? error.message : String(error)}`);
207
+ throw new MarketFetchError('io', `market fetch failed (${file}): network error`);
207
208
  } finally {
208
209
  void reader.cancel().catch(() => {});
209
210
  reader.releaseLock();
@@ -0,0 +1,106 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { closeSync, existsSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { elanousStateRoot } from '../../autopilot/state-paths.js';
5
+ import { loadPluginManifestFromDir } from '../core/manifest.js';
6
+ import { listInstalledPlugins, withLedgerLock } from './plugin-install.js';
7
+
8
+ const PLUGIN_NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
9
+ const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
10
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
11
+
12
+ type FieldStatus = { name: string; env: string; set: boolean };
13
+
14
+ function credentialPath(name: string, root: string): string {
15
+ if (!PLUGIN_NAME.test(name)) throw new Error('invalid plugin name');
16
+ return join(root, 'plugins', 'credentials', `${name}.json`);
17
+ }
18
+
19
+ function declaredFields(name: string, root: string): Array<{ name: string; env: string }> {
20
+ if (!PLUGIN_NAME.test(name)) throw new Error('invalid plugin name');
21
+ const installed = listInstalledPlugins(root).filter(plugin => plugin.name === name);
22
+ if (installed.length === 0) throw new Error(`plugin not installed: ${name}`);
23
+ const fields = new Map<string, string>();
24
+ for (const plugin of installed) {
25
+ const manifest = loadPluginManifestFromDir(plugin.path, { id: plugin.name }).manifest;
26
+ for (const connector of manifest.contributes.connectors ?? []) {
27
+ for (const field of [
28
+ ...(connector.fields ?? []).map(item => ({ name: item.name, env: item.env })),
29
+ ...(connector.userConfig ?? []).map(item => ({ name: item.key, env: item.env })),
30
+ ]) {
31
+ if (!FIELD_NAME.test(field.name)) throw new Error('invalid plugin credential field name');
32
+ const env = field.env === undefined
33
+ ? `ELANOUS_PLUGIN_${name.replaceAll('-', '_').toUpperCase()}_${field.name.replaceAll('-', '_').toUpperCase()}`
34
+ : field.env;
35
+ if (typeof env !== 'string' || !ENV_NAME.test(env)) throw new Error('invalid plugin credential environment name');
36
+ if (fields.has(field.name) && fields.get(field.name) !== env) throw new Error('conflicting plugin credential environment name');
37
+ fields.set(field.name, env);
38
+ }
39
+ }
40
+ }
41
+ if (new Set(fields.values()).size !== fields.size) throw new Error('duplicate plugin credential environment name');
42
+ return [...fields].map(([fieldName, env]) => ({ name: fieldName, env })).sort((a, b) => a.name.localeCompare(b.name));
43
+ }
44
+
45
+ function readCredentials(path: string): Record<string, string> {
46
+ if (!existsSync(path)) return Object.create(null) as Record<string, string>;
47
+ if (!lstatSync(path).isFile()) throw new Error('invalid plugin credential file');
48
+ let parsed: unknown;
49
+ try { parsed = JSON.parse(readFileSync(path, 'utf8')); }
50
+ catch { throw new Error('invalid plugin credential file'); }
51
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)
52
+ || Object.entries(parsed).some(([key, value]) => !FIELD_NAME.test(key) || typeof value !== 'string')) {
53
+ throw new Error('invalid plugin credential file');
54
+ }
55
+ return Object.assign(Object.create(null) as Record<string, string>, parsed);
56
+ }
57
+
58
+ /** Update one installed plugin's declared fields. null removes a field; omitted fields remain intact. */
59
+ export function setPluginCredentials(name: string, fields: Record<string, string | null>, root = elanousStateRoot()): { set: string[] } {
60
+ const path = credentialPath(name, root);
61
+ if (!fields || typeof fields !== 'object' || Array.isArray(fields)) throw new Error('invalid plugin credential fields');
62
+ const allowed = new Set(declaredFields(name, root).map(field => field.name));
63
+ for (const [key, value] of Object.entries(fields)) {
64
+ if (!allowed.has(key) || (typeof value !== 'string' && value !== null) || (typeof value === 'string' && value.includes('\0'))) {
65
+ throw new Error('invalid plugin credential field');
66
+ }
67
+ }
68
+ return withLedgerLock(root, () => {
69
+ const current = readCredentials(path);
70
+ for (const [key, value] of Object.entries(fields)) {
71
+ if (value === null) delete current[key];
72
+ else current[key] = value;
73
+ }
74
+ if (Object.keys(fields).length === 0) return { set: [] };
75
+ mkdirSync(join(root, 'plugins', 'credentials'), { recursive: true, mode: 0o700 });
76
+ const temp = `${path}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
77
+ try {
78
+ const fd = openSync(temp, 'wx', 0o600);
79
+ try {
80
+ fchmodSync(fd, 0o600);
81
+ writeFileSync(fd, JSON.stringify(current));
82
+ fsyncSync(fd);
83
+ } finally { closeSync(fd); }
84
+ renameSync(temp, path);
85
+ } finally { rmSync(temp, { force: true }); }
86
+ return { set: Object.keys(fields).filter(key => Object.hasOwn(current, key)).sort() };
87
+ });
88
+ }
89
+
90
+ /** Public metadata only: values never leave the credential store. */
91
+ export function credentialStatus(name: string, root = elanousStateRoot()): { fields: FieldStatus[] } {
92
+ const declared = declaredFields(name, root);
93
+ const saved = readCredentials(credentialPath(name, root));
94
+ return { fields: declared.map(field => ({ ...field, set: Object.hasOwn(saved, field.name) })) };
95
+ }
96
+
97
+ /** Environment entries intended only for this plugin's child process. */
98
+ export function pluginEnv(name: string, root = elanousStateRoot()): Record<string, string> {
99
+ const declared = declaredFields(name, root);
100
+ const saved = readCredentials(credentialPath(name, root));
101
+ const env = Object.create(null) as Record<string, string>;
102
+ for (const field of declared) {
103
+ if (Object.hasOwn(saved, field.name)) env[field.env] = saved[field.name]!;
104
+ }
105
+ return env;
106
+ }