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
@@ -3,15 +3,16 @@
3
3
  // 사람은 무엇을 해야 할지 몰랐다(🅢 09-28 · 대표 승인 수리). Elanous 는 OAuth 를 쓰지 않는다 — 고정 Bearer 토큰이다.
4
4
  // ⇒ 두 자리에서 «할 일»을 말한다: ① 401 의 `WWW-Authenticate` ② `/register` 의 RFC 7591 오류 본문(Claude Code 가 그대로 보여 준다).
5
5
 
6
- /** 토큰 «값»은 절대 싣지 않는다 — 파일 경로와 명령만. */
7
- export const MCP_AUTH_HINT =
8
- 'Elanous 는 OAuth 를 쓰지 않는다(고정 Bearer 토큰). 권장: stdio 로 등록 — `claude mcp add elanous -- elanous mcp serve`. '
9
- + 'HTTP 가 필요하면 ~/.elanous/acp-token 의 값을 Authorization 헤더로: '
10
- + '`claude mcp add --transport http elanous http://127.0.0.1:31415/v1/mcp --header "Authorization: Bearer <acp-token>"`';
6
+ /** 토큰 «값»은 절대 싣지 않는다 — 파일 경로와 명령만. 주소는 포트를 짐작하지 않고 «요청이 닿은 origin»을 쓴다(대표 09-28 통합관제). */
7
+ export function mcpAuthHint(origin: string): string {
8
+ return 'Elanous 는 OAuth 를 쓰지 않는다(고정 Bearer 토큰). 권장: stdio 로 등록 — `claude mcp add elanous -- elanous mcp serve`. '
9
+ + 'HTTP 가 필요하면 ~/.elanous/acp-token 의 값을 Authorization 헤더로: '
10
+ + `\`claude mcp add --transport http elanous ${origin}/v1/mcp --header "Authorization: Bearer <acp-token>"\``;
11
+ }
11
12
 
12
13
  /** `/v1/mcp` 401 — 무엇을 해야 하는지 헤더로도 말한다(WWW-Authenticate 의 error_description 은 ASCII 만). */
13
- export function mcpUnauthorizedResponse(): Response {
14
- return new Response(JSON.stringify({ error: 'unauthorized', hint: MCP_AUTH_HINT }), {
14
+ export function mcpUnauthorizedResponse(origin: string): Response {
15
+ return new Response(JSON.stringify({ error: 'unauthorized', hint: mcpAuthHint(origin) }), {
15
16
  status: 401,
16
17
  headers: {
17
18
  'content-type': 'application/json; charset=utf-8',
@@ -22,8 +23,8 @@ export function mcpUnauthorizedResponse(): Response {
22
23
  }
23
24
 
24
25
  /** OAuth 동적 클라이언트 등록 거절 — RFC 7591 §3.2.2 오류 형식(Claude Code 가 본문을 사람에게 보여 준다). */
25
- export function oauthRegistrationRejected(): Response {
26
- return new Response(JSON.stringify({ error: 'invalid_client_metadata', error_description: MCP_AUTH_HINT }), {
26
+ export function oauthRegistrationRejected(origin: string): Response {
27
+ return new Response(JSON.stringify({ error: 'invalid_client_metadata', error_description: mcpAuthHint(origin) }), {
27
28
  status: 400,
28
29
  headers: { 'content-type': 'application/json; charset=utf-8', 'access-control-allow-origin': '*' },
29
30
  });
@@ -98,13 +98,14 @@ function asItem(pr: RawPr) {
98
98
  }
99
99
 
100
100
  // A page load fires the list twice (the daemon client settles) and polls cards — identical reads share one gh call.
101
+ // Failures are shared too: during a GitHub rate-limit window a retry per request only deepens the limit (GIT-S83).
101
102
  const readCache = new Map<string, { at: number; value: Promise<GhResult> }>();
102
103
  export function clearApprovalsReadCache(): void { readCache.clear(); }
103
104
  function cachedRead(args: string[], ttlMs: number, now: number, call: () => Promise<GhResult>): Promise<GhResult> {
104
105
  const key = args.join('\u0000');
105
106
  const hit = readCache.get(key);
106
107
  if (hit && now - hit.at < ttlMs) return hit.value;
107
- const value = call().then((result) => { if (!result.ok) readCache.delete(key); return result; });
108
+ const value = call();
108
109
  readCache.set(key, { at: now, value });
109
110
  return value;
110
111
  }
@@ -254,6 +254,10 @@ function decideAuth(req: Request, opts: MetaApiOpts): { ok: boolean; reason: Aut
254
254
  if (isSameOriginRequest(req, peerAddress)) return { ok: true, reason: 'same-origin' };
255
255
  const peerRejected = req.headers.get('sec-fetch-site') === 'same-origin'
256
256
  && !isTrustedSameOriginPeer(peerAddress);
257
+ return decideBearer(req, opts, peerRejected);
258
+ }
259
+
260
+ function decideBearer(req: Request, opts: MetaApiOpts, peerRejected = false): { ok: boolean; reason: AuthReason } {
257
261
  const token = opts.bearerToken;
258
262
  if (!token) return { ok: false, reason: peerRejected ? 'untrusted-same-origin-peer' : 'no-bearer-configured' };
259
263
  const auth = req.headers.get('authorization') ?? '';
@@ -273,6 +277,16 @@ function decideAuth(req: Request, opts: MetaApiOpts): { ok: boolean; reason: Aut
273
277
  : { ok: false, reason: 'bearer-mismatch' };
274
278
  }
275
279
 
280
+ /** Bearer-only credential check that ignores the same-origin shortcut. `checkAuth` returns
281
+ * `same-origin` before it ever looks at the Authorization header, so a paired PWA that sends its
282
+ * token is still reported as same-origin — write paths that must not trust same-origin alone
283
+ * (graph approvals) ask this instead. Returns the credential kind, or undefined if none matched. */
284
+ export function bearerCredential(req: Request, opts: MetaApiOpts): 'noauth' | 'bearer-match' | 'temp-token' | undefined {
285
+ if (opts.noAuth) return 'noauth';
286
+ const decision = decideBearer(req, opts);
287
+ return decision.ok && (decision.reason === 'bearer-match' || decision.reason === 'temp-token') ? decision.reason : undefined;
288
+ }
289
+
276
290
  // Exported so sibling NEXUS API modules (e.g. T5.G `tasks-scheduler.ts`)
277
291
  // can reuse the exact same gate — same-origin + constant-time bearer
278
292
  // check — without duplicating security-sensitive code.
@@ -0,0 +1,47 @@
1
+ import { debug } from '../../debug/log.js';
2
+ import { credentialStatus, setPluginCredentials } from '../../plugins/install/plugin-credentials.js';
3
+ import { checkAuth, type MetaApiOpts } from './meta-api.js';
4
+ import { jsonResponse } from './json-response.js';
5
+
6
+ const NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
7
+
8
+ function failure(error: unknown): Response {
9
+ if (error instanceof Error && error.message.startsWith('plugin not installed:')) return jsonResponse({ error: 'not_found' }, 404);
10
+ if (error instanceof Error && (error.message === 'invalid plugin name' || error.message === 'invalid plugin credential field'
11
+ || error.message === 'invalid plugin credential fields')) {
12
+ return jsonResponse({ error: 'bad_request' }, 400);
13
+ }
14
+ return jsonResponse({ error: 'credential_error' }, 500);
15
+ }
16
+
17
+ export function handlePluginsCredentialsGet(req: Request, name: string, opts: MetaApiOpts, root?: string): Response {
18
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
19
+ if (!NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
20
+ try { return jsonResponse(credentialStatus(name, root), 200); }
21
+ catch (error) { return failure(error); }
22
+ }
23
+
24
+ /** `reload` re-spawns the daemon's MCP clients so a running plugin server picks up the new values
25
+ * (the spawn reads `pluginEnv` fresh). A failed reload never fails the write — the reply says so. */
26
+ export async function handlePluginsCredentialsPut(req: Request, name: string, opts: MetaApiOpts, root?: string, reload?: () => Promise<unknown>): Promise<Response> {
27
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
28
+ if (!NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
29
+ let body: unknown;
30
+ try { body = await req.json(); }
31
+ catch { return jsonResponse({ error: 'bad_request' }, 400); }
32
+ const fields = body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>).fields : undefined;
33
+ if (!fields || typeof fields !== 'object' || Array.isArray(fields)
34
+ || Object.values(fields).some(value => typeof value !== 'string' && value !== null)) {
35
+ return jsonResponse({ error: 'bad_request' }, 400);
36
+ }
37
+ try {
38
+ const result = setPluginCredentials(name, fields as Record<string, string | null>, root);
39
+ debug.log('plugin.credentials', 'set', { plugin: name, fields: Object.keys(fields), count: Object.keys(fields).length });
40
+ let reloaded = false;
41
+ if (reload) {
42
+ try { await reload(); reloaded = true; }
43
+ catch { debug.log('plugin.credentials', 'reload-failed', { plugin: name }, { level: 'warn' }); }
44
+ }
45
+ return jsonResponse({ ...result, reloaded }, 200);
46
+ } catch (error) { return failure(error); }
47
+ }
@@ -4,7 +4,8 @@ import { elanousStateRoot } from '../../autopilot/state-paths.js';
4
4
  import { debug } from '../../debug/log.js';
5
5
  import { OFFICIAL_INDEX_KEYS } from '../../market/official-keys.js';
6
6
  import { verifyIndex, type MarketplaceIndex } from '../../market/signed-index.js';
7
- import { listInstalledPlugins } from '../../plugins/install/plugin-install.js';
7
+ import { installPlugin, listInstalledPlugins, removePlugin, type InstallEvent } from '../../plugins/install/plugin-install.js';
8
+ import { updateMarket, type MarketFetchOptions } from '../../plugins/install/market-fetch.js';
8
9
  import { userConfigPath } from '../../user-config.js';
9
10
  import { jsonResponse } from './json-response.js';
10
11
  import { checkAuth, type MetaApiOpts } from './meta-api.js';
@@ -66,7 +67,7 @@ export function readMarketIndex(root = elanousStateRoot(), trustedKeys: Readonly
66
67
  const dir = join(base, entry.name);
67
68
  const indexPath = [join(dir, 'marketplace.json'), join(dir, '.agents', 'plugins', 'marketplace.json')].find(existsSync);
68
69
  if (!indexPath) {
69
- markets.push({ name: entry.name, signature: 'malformed', detail: 'marketplace.json: not found', plugins: [] });
70
+ markets.push({ name: entry.name, signature: 'malformed', detail: '마켓 인덱스를 찾지 못했습니다.', plugins: [] });
70
71
  continue;
71
72
  }
72
73
  let signature: MarketSignature = 'missing';
@@ -93,7 +94,7 @@ export function readMarketIndex(root = elanousStateRoot(), trustedKeys: Readonly
93
94
  signature = 'malformed';
94
95
  detail = 'marketplace.json: invalid market index';
95
96
  }
96
- markets.push({ name: entry.name, signature, ...(detail ? { detail } : {}), plugins });
97
+ markets.push({ name: entry.name, signature, ...(detail ? { detail: '마켓 인덱스를 확인하지 못했습니다.' } : {}), plugins });
97
98
  }
98
99
  markets.sort((a, b) => a.name.localeCompare(b.name));
99
100
  debug.log('nexus.plugins-market', 'index', { markets: markets.length, ok: markets.filter(m => m.signature === 'ok').length, notOk: markets.filter(m => m.signature !== 'ok').length });
@@ -123,3 +124,86 @@ export function handlePluginsGet(req: Request, opts: MetaApiOpts, root?: string)
123
124
  if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
124
125
  return jsonResponse(listInstalledPlugins(root));
125
126
  }
127
+
128
+ const MARKET_NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
129
+ const MARKET_SPEC = /^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/;
130
+
131
+ /** 실패 원인 한 줄 — 사용자가 «왜» 를 볼 수 있게(2026-09-29 실물: «설치 중 오류가 발생했습니다» 만 보여 아티팩트 누락을 못 봤다). 홈 경로는 `~` 로 · 200자 · 한 줄. */
132
+ export function safeDetail(error: unknown): string | undefined {
133
+ const message = error instanceof Error ? error.message : typeof error === 'string' ? error : undefined;
134
+ if (!message) return undefined;
135
+ return message.replace(/\/(?:Users|home)\/[^/\s]+/g, '~').replace(/\s+/g, ' ').trim().slice(0, 200) || undefined;
136
+ }
137
+
138
+ function safeReason(error: unknown): string {
139
+ const reason = error && typeof error === 'object' && 'reason' in error ? error.reason : undefined;
140
+ return typeof reason === 'string' && ['signature', 'scan', 'consent-denied', 'credentials', 'conflict', 'io'].includes(reason)
141
+ ? reason : 'io';
142
+ }
143
+
144
+ export async function handlePluginsMarketRefresh(req: Request, name: string, opts: MetaApiOpts, fetchOptions: MarketFetchOptions = {}): Promise<Response> {
145
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
146
+ if (!MARKET_NAME.test(name)) return jsonResponse({ ok: false, reason: 'invalid-market' }, 400);
147
+ try {
148
+ const result = await updateMarket(name, fetchOptions);
149
+ const plugins = projectPlugins(result.index, name);
150
+ debug.log('nexus.plugins-market', 'refresh', { name, ok: true, count: plugins.length });
151
+ return jsonResponse({ ok: true, plugins });
152
+ } catch (error) {
153
+ const reason = safeReason(error);
154
+ debug.log('nexus.plugins-market', 'refresh', { name, ok: false, reason });
155
+ return jsonResponse({ ok: false, reason });
156
+ }
157
+ }
158
+
159
+ export async function handlePluginsInstall(req: Request, opts: MetaApiOpts, root?: string, configPath?: string): Promise<Response> {
160
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
161
+ let body: unknown;
162
+ try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
163
+ const input = body as { spec?: unknown; acceptedCapabilities?: unknown } | null;
164
+ if (!input || typeof input.spec !== 'string' || !MARKET_SPEC.test(input.spec) ||
165
+ !Array.isArray(input.acceptedCapabilities) || !input.acceptedCapabilities.every(cap => typeof cap === 'string')) {
166
+ return jsonResponse({ error: 'bad_request' }, 400);
167
+ }
168
+ const spec = input.spec;
169
+ const accepted = new Set(input.acceptedCapabilities as string[]);
170
+ const trustedKeys = configuredTrustedKeys(configPath);
171
+ const stream = new ReadableStream<Uint8Array>({
172
+ async start(controller) {
173
+ const encoder = new TextEncoder();
174
+ const send = (value: Record<string, unknown>) => controller.enqueue(encoder.encode(`${JSON.stringify(value)}\n`));
175
+ const onEvent = (event: InstallEvent) => {
176
+ if (event.event === 'consent') send({ event: 'consent', capabilities: event.capabilities, required: event.required });
177
+ else if (event.event === 'credentials') send({ event: 'credentials', required: event.connectors.some(c => c.fields.length > 0) });
178
+ else send({ event: event.event });
179
+ };
180
+ try {
181
+ await installPlugin(spec, {
182
+ ...(root ? { root } : {}), trustedKeys: [...OFFICIAL_INDEX_KEYS, ...trustedKeys],
183
+ onEvent, consent: capabilities => capabilities.every(cap => accepted.has(cap)),
184
+ });
185
+ debug.log('nexus.plugins-market', 'install', { spec, ok: true });
186
+ } catch (error) {
187
+ const reason = safeReason(error);
188
+ const detail = safeDetail(error);
189
+ debug.log('nexus.plugins-market', 'install', { spec, ok: false, reason, ...(detail ? { detail } : {}) });
190
+ send({ event: 'failed', reason, ...(detail ? { detail } : {}) });
191
+ } finally { controller.close(); }
192
+ },
193
+ });
194
+ return new Response(stream, { headers: { 'content-type': 'application/x-ndjson; charset=utf-8', 'cache-control': 'no-store' } });
195
+ }
196
+
197
+ export function handlePluginsRemove(req: Request, name: string, opts: MetaApiOpts, root?: string): Response {
198
+ if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
199
+ if (!MARKET_NAME.test(name)) return jsonResponse({ error: 'bad_request' }, 400);
200
+ try {
201
+ const removed = removePlugin(name, root);
202
+ debug.log('nexus.plugins-market', 'remove', { name, removed });
203
+ return jsonResponse({ removed });
204
+ } catch (error) {
205
+ const reason = safeReason(error);
206
+ debug.log('nexus.plugins-market', 'remove', { name, reason });
207
+ return jsonResponse({ error: reason }, 500);
208
+ }
209
+ }
@@ -4,10 +4,14 @@
4
4
  * 응답 키는 `key`·`expires_at` 둘뿐. refresh·계정 id·파일 경로는 응답·로그·에러에 싣지 않는다.
5
5
  */
6
6
  import { debug } from '../../debug/log.js';
7
- import { verifyGroundingToken, type GroundingTokenDeps } from '../../grounding/token.js';
7
+ import { githubInstallationCredential } from '../../auth/github-app-token.js';
8
+ import { verifyGroundingToken, type GroundingTokenDeps, type GroundingTokenScope } from '../../grounding/token.js';
8
9
  import { isGrokSubscriptionExpiring, readGrokSubscriptionAccess, refreshGrokSubscriptionToken } from '../../grok/credential.js';
9
10
 
10
11
  export const POD_CREDENTIAL_GROK_PATH = '/v1/pod/credential/grok';
12
+ export const POD_CREDENTIAL_GITHUB_PATH = '/v1/pod/credential/github';
13
+ export const POD_GITHUB_CREDENTIAL_TOKEN_ENV = 'ELANOUS_POD_GITHUB_CREDENTIAL_TOKEN';
14
+ export const POD_GITHUB_CREDENTIAL_URL_ENV = 'ELANOUS_POD_GITHUB_CREDENTIAL_URL';
11
15
  /** 만료까지 이 안이면 기존 호스트 갱신(`refreshGrokSubscriptionToken`)을 한 번 부른다. */
12
16
  export const POD_CREDENTIAL_HOST_REFRESH_WINDOW_MS = 10 * 60 * 1000;
13
17
  /** runId 당 분당 1회. */
@@ -27,34 +31,45 @@ export interface PodCredentialDeps {
27
31
  readonly loadAccess?: (home: string | undefined, env: NodeJS.ProcessEnv) => { key: string; expiresAt: string } | null;
28
32
  }
29
33
 
34
+ export interface PodGithubCredentialDeps {
35
+ readonly token?: GroundingTokenDeps;
36
+ readonly now?: () => number;
37
+ readonly mintInstallation?: (repository: string) => { token: string; expires_at: string } | null;
38
+ readonly installationRepositories?: (token: string) => Promise<readonly string[] | null>;
39
+ }
40
+
30
41
  interface HitWindow {
31
42
  readonly stamps: number[];
32
43
  }
33
44
 
34
45
  const hitsByRun = new Map<string, HitWindow>();
35
46
  const globalHits: number[] = [];
47
+ const githubHitsByRun = new Map<string, HitWindow>();
48
+ const githubGlobalHits: number[] = [];
36
49
 
37
50
  export function resetPodCredentialRateForTesting(): void {
38
51
  hitsByRun.clear();
39
52
  globalHits.length = 0;
53
+ githubHitsByRun.clear();
54
+ githubGlobalHits.length = 0;
40
55
  }
41
56
 
42
57
  function prune(stamps: number[], now: number): number[] {
43
58
  return stamps.filter((t) => now - t < 60_000);
44
59
  }
45
60
 
46
- function takeRate(runId: string, now: number): boolean {
47
- const prev = prune(hitsByRun.get(runId)?.stamps ?? [], now);
48
- const global = prune(globalHits, now);
49
- globalHits.length = 0;
50
- globalHits.push(...global);
51
- if (prev.length >= POD_CREDENTIAL_PER_RUN_PER_MIN || globalHits.length >= POD_CREDENTIAL_GLOBAL_PER_MIN) {
52
- hitsByRun.set(runId, { stamps: prev });
61
+ function takeRate(runId: string, now: number, byRun = hitsByRun, all = globalHits): boolean {
62
+ const prev = prune(byRun.get(runId)?.stamps ?? [], now);
63
+ const global = prune(all, now);
64
+ all.length = 0;
65
+ all.push(...global);
66
+ if (prev.length >= POD_CREDENTIAL_PER_RUN_PER_MIN || all.length >= POD_CREDENTIAL_GLOBAL_PER_MIN) {
67
+ byRun.set(runId, { stamps: prev });
53
68
  return false;
54
69
  }
55
70
  prev.push(now);
56
- globalHits.push(now);
57
- hitsByRun.set(runId, { stamps: prev });
71
+ all.push(now);
72
+ byRun.set(runId, { stamps: prev });
58
73
  return true;
59
74
  }
60
75
 
@@ -109,25 +124,64 @@ export async function handlePodGrokCredential(req: Request, deps: PodCredentialD
109
124
  return Response.json({ key: loaded.key, expires_at: expiresAt });
110
125
  }
111
126
 
127
+ export async function installationRepositories(token: string): Promise<readonly string[] | null> {
128
+ const response = await fetch('https://api.github.com/installation/repositories?per_page=100', {
129
+ headers: { authorization: `Bearer ${token}`, accept: 'application/vnd.github+json', 'x-github-api-version': '2022-11-28' },
130
+ signal: AbortSignal.timeout(10_000),
131
+ });
132
+ if (!response.ok) return null;
133
+ const data: unknown = await response.json();
134
+ if (!data || typeof data !== 'object' || Array.isArray(data) || !Array.isArray((data as { repositories?: unknown }).repositories)) return null;
135
+ const body = data as { total_count?: unknown; repositories: unknown[] };
136
+ if (body.repositories.length !== 1 || body.total_count !== 1) return null;
137
+ const name = body.repositories[0] && typeof body.repositories[0] === 'object' && !Array.isArray(body.repositories[0]) ? (body.repositories[0] as { full_name?: unknown }).full_name : undefined;
138
+ return typeof name === 'string' ? [name] : null;
139
+ }
140
+
141
+ /** No repository is accepted from the request body: the host signed its owner/name into the run token. */
142
+ export async function handlePodGithubCredential(req: Request, deps: PodGithubCredentialDeps = {}): Promise<Response> {
143
+ const gate = await authenticatePodCredential(req, deps, 'gh-credential');
144
+ if (!gate.ok) return gate.response;
145
+ if (!gate.repository || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(gate.repository)) return Response.json({ error: 'unauthorized' }, { status: 401 });
146
+ if (!takeRate(gate.runId, (deps.now ?? Date.now)(), githubHitsByRun, githubGlobalHits)) return reasonResponse(429, 'rate');
147
+
148
+ let issued: { token: string; expires_at: string } | null;
149
+ try {
150
+ issued = (deps.mintInstallation ?? ((repository) => githubInstallationCredential({ scope: { repository: repository.split('/')[1]! } })))(gate.repository);
151
+ } catch {
152
+ return reasonResponse(503, 'unavailable');
153
+ }
154
+ if (!issued?.token || typeof issued.expires_at !== 'string' || !Number.isFinite(Date.parse(issued.expires_at)) || Date.parse(issued.expires_at) <= (deps.now ?? Date.now)()) return reasonResponse(503, 'unavailable');
155
+ try {
156
+ const available = await (deps.installationRepositories ?? installationRepositories)(issued.token);
157
+ if (available?.length !== 1 || available[0]?.toLowerCase() !== gate.repository.toLowerCase()) return reasonResponse(503, 'unavailable');
158
+ } catch {
159
+ return reasonResponse(503, 'unavailable');
160
+ }
161
+ debug.log('pod.credential', 'github-issued', { runId: gate.runId, job: gate.job, expiresAt: issued.expires_at });
162
+ return Response.json({ token: issued.token, expires_at: issued.expires_at });
163
+ }
164
+
112
165
  /**
113
166
  * 쓰기 관문. 본문을 읽지 않는다. 토큰이 없거나 서명이 깨지면 401,
114
167
  * scope·만료·회수는 403 과 사유만.
115
168
  */
116
169
  export async function authenticatePodCredential(
117
170
  req: Request,
118
- deps: PodCredentialDeps = {},
171
+ deps: Pick<PodCredentialDeps, 'token'> = {},
172
+ expectedScope: GroundingTokenScope = 'llm-credential',
119
173
  ): Promise<
120
- | { readonly ok: true; readonly runId: string; readonly job: string; readonly exp: number }
174
+ | { readonly ok: true; readonly runId: string; readonly job: string; readonly repository?: string; readonly exp: number }
121
175
  | { readonly ok: false; readonly response: Response }
122
176
  > {
123
177
  const token = bearerOf(req);
124
178
  if (!token) return { ok: false, response: Response.json({ error: 'unauthorized' }, { status: 401 }) };
125
- const verified = await verifyGroundingToken(token, { ...(deps.token ?? {}), expectedScope: 'llm-credential' });
179
+ const verified = await verifyGroundingToken(token, { ...(deps.token ?? {}), expectedScope });
126
180
  if (!verified.ok) {
127
181
  if (verified.reason === 'expired' || verified.reason === 'revoked' || verified.reason === 'scope') {
128
182
  return { ok: false, response: reasonResponse(403, verified.reason) };
129
183
  }
130
184
  return { ok: false, response: Response.json({ error: 'unauthorized' }, { status: 401 }) };
131
185
  }
132
- return { ok: true, runId: verified.runId, job: verified.job, exp: verified.exp };
186
+ return { ok: true, runId: verified.runId, job: verified.job, ...(verified.repository ? { repository: verified.repository } : {}), exp: verified.exp };
133
187
  }
@@ -49,6 +49,13 @@ export const PUBLIC_ROUTES: Array<PublicRoute> = [
49
49
  selfVerified: true,
50
50
  why: 'Pod grok access relay (#20789); Pods hold no nexus bearer — pod-credential-api.ts verifies its own llm-credential token (verifyGroundingToken expectedScope) before reading the body',
51
51
  },
52
+ {
53
+ method: 'POST',
54
+ match: 'exact',
55
+ path: '/v1/pod/credential/github',
56
+ selfVerified: true,
57
+ why: 'Pod GitHub App credential relay verifies its own run-scoped gh-credential token before minting a repository-scoped installation token',
58
+ },
52
59
  {
53
60
  method: 'GET',
54
61
  match: 'prefix',
@@ -1,12 +1,13 @@
1
1
  import { getDefaultLogStore, resolveLogInstanceName, LogStore, type LogStoreRow, type LogQuery } from '../../mss/logging/log-store.js';
2
2
  import { readLogInstances, type LogInstanceView } from '../../mss/logging/instance-registry.js';
3
- import { existsSync } from 'node:fs';
3
+ import { existsSync, statSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { prodInstanceRoot } from '../../instance/resolve.js';
6
6
  import { debug, redactSecretText, redactSecrets } from '../../debug/log.js';
7
- import { activeStoreNames, parseSinceParam, type LogFabricDeps } from './log-fabric.js';
7
+ import { activeStoreNames, parseSinceParam, type LogFabricDeps, registeredLogStoreCount } from './log-fabric.js';
8
8
  import { checkAuth, type MetaApiOpts } from './meta-api.js';
9
9
  import { jsonResponse } from './json-response.js';
10
+ import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
10
11
 
11
12
  export type TraceLevel = 'L0' | 'L1' | 'L2' | 'L3';
12
13
  export interface TraceEvent {
@@ -201,12 +202,19 @@ export function buildRunTrace(
201
202
 
202
203
  export interface TraceDeps extends LogFabricDeps {
203
204
  queryLog?: (details: { stores: string[]; level: TraceLevel; count: number; truncated: boolean; mode: 'rows' | 'runs'; runs: number }) => void;
205
+ ledgerStat?: (path: string) => void;
204
206
  }
205
207
  function storeNames(url: URL, deps: TraceDeps): string[] {
206
208
  const names = [...new Set(url.searchParams.getAll('store').flatMap((v) => v.split(',')).map((v) => v.trim()).filter(Boolean))];
207
- return names.includes('@active') ? [...new Set([...activeStoreNames(deps), ...names.filter((n) => n !== '@active')])]
208
- : (names.length ? names : [url.searchParams.get('universe') || resolveLogInstanceName()]);
209
+ if (names.includes('@active')) {
210
+ let active: string[];
211
+ try { active = activeStoreNames(deps); }
212
+ catch { active = [resolveLogInstanceName()]; }
213
+ return [...new Set([...active, ...names.filter((n) => n !== '@active')])];
214
+ }
215
+ return names.length ? names : [url.searchParams.get('universe') || resolveLogInstanceName()];
209
216
  }
217
+ const registeredStoreCount = (deps: TraceDeps): number | null => registeredLogStoreCount(deps);
210
218
  function selectedStores(names: string[], deps: TraceDeps): { name: string; store: LogStore | null; remote: boolean; error?: string }[] {
211
219
  const selfName = resolveLogInstanceName();
212
220
  const self = names.includes(selfName) ? (deps.store ?? getDefaultLogStore)() : null;
@@ -266,6 +274,22 @@ function sortRows(a: LogStoreRow, b: LogStoreRow): number {
266
274
  return b.ts_ms - a.ts_ms || b.id - a.id || a.instance.localeCompare(b.instance);
267
275
  }
268
276
 
277
+ function findRunUniverse(runId: string, selected: readonly string[], deps: TraceDeps): { universe?: string; checked: number } {
278
+ let checked = 0;
279
+ const views = (deps.instances ?? readLogInstances)();
280
+ for (const view of [...views.filter((v) => selected.includes(v.name)), ...views.filter((v) => !selected.includes(v.name))]) {
281
+ checked++;
282
+ try {
283
+ (deps.ledgerStat ?? statSync)(runLedgerPath(runId, runLedgerDir(view.stateDir)));
284
+ return { universe: view.name, checked };
285
+ } catch (error) {
286
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
287
+ throw new Error(`unable to check run ledger for '${view.name}': ${error instanceof Error ? error.message : String(error)}`);
288
+ }
289
+ }
290
+ return { checked };
291
+ }
292
+
269
293
  /** Read-only bounded log-fabric fan-in, authenticated before any store access. */
270
294
  export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {}): Response {
271
295
  if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
@@ -300,7 +324,7 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
300
324
  const trace = buildRunTrace(runs, parsed.level as 'L0' | 'L1');
301
325
  trace.truncated = runLimitReached || failures.length > 0;
302
326
  try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'runs', runs: runs.length }); } catch { /* query logging must not block reads */ }
303
- return jsonResponse({ ok: true, ...trace, stores: names, ...(failures.length ? { failedStores: failures } : {}) }, 200);
327
+ return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(failures.length ? { failedStores: failures } : {}) }, 200);
304
328
  }
305
329
  const failures: Array<{ name: string; reason: string }> = [];
306
330
  const rows: LogStoreRow[] = [];
@@ -321,10 +345,37 @@ export function handleTrace(req: Request, opts: MetaApiOpts, deps: TraceDeps = {
321
345
  } catch (e) { failures.push({ name: entry.name, reason: e instanceof Error ? e.message : String(e) }); }
322
346
  }
323
347
  if (!opened) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
348
+ const runId = url.searchParams.get('runId');
349
+ let resolution: { universe?: string; checked: number } | undefined;
350
+ if ((parsed.level === 'L2' || parsed.level === 'L3') && runId && !rows.some((row) => matches(row, url)) && !requestedUniverse) {
351
+ // A store with no log db at all («has no log store yet» — e.g. the daemon's own tree-derived universe) cannot
352
+ // hold the run, so it must not block the ledger lookup. A store that exists but failed to open/read still does:
353
+ // «could not read» is never answered as «not there». The reply keeps `failedStores` either way.
354
+ const blocking = failures.filter((failure) => !/has no log store yet/.test(failure.reason));
355
+ if (blocking.length) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: failures }, 503);
356
+ try { resolution = findRunUniverse(runId, names, deps); }
357
+ catch (error) { return jsonResponse({ ok: false, error: 'run-universe-unavailable', reason: error instanceof Error ? error.message : String(error) }, 503); }
358
+ try { debug.log('nexus.trace', 'run-universe-resolved', { runId, universe: resolution.universe ?? 'not-found', checked: resolution.checked }); } catch { /* observation must not block reads */ }
359
+ if (resolution.universe && !names.includes(resolution.universe)) {
360
+ const name = resolution.universe;
361
+ names.push(name);
362
+ const [entry] = selectedStores([name], deps);
363
+ if (!entry?.store) return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: [{ name, reason: entry?.error ?? 'log-store-unavailable' }] }, 503);
364
+ stores.push(entry);
365
+ try {
366
+ const query: LogQuery = { sinceMs: parsed.from, untilMs: parsed.to, limit: TRACE_LIMIT + 1 };
367
+ const search = url.searchParams.get('q');
368
+ if (search) query.grep = search;
369
+ const page = entry.store.queryTraceRun(runId, query);
370
+ if (page.length > TRACE_LIMIT) candidateLimitReached = true;
371
+ rows.push(...page.map((row) => ({ ...row, instance: row.instance || name })));
372
+ } catch (e) { return jsonResponse({ ok: false, error: 'log-store-unavailable', failedStores: [{ name, reason: e instanceof Error ? e.message : String(e) }] }, 503); }
373
+ }
374
+ }
324
375
  const filtered = rows.filter((row) => matches(row, url)).sort(sortRows);
325
376
  const trace = buildTrace(filtered.slice(0, parsed.limit), parsed.level, filtered.length > parsed.limit || candidateLimitReached || failures.length > 0);
326
377
  try { (deps.queryLog ?? ((details) => debug.log('logs.trace', 'query', details)))({ stores: names, level: parsed.level, count: trace.events.length, truncated: trace.truncated, mode: 'rows', runs: trace.nodes.filter((node) => node.level === 'L1' && node.count > 0).length }); } catch { /* query logging must not block reads */ }
327
- return jsonResponse({ ok: true, ...trace, stores: names, ...(failures.length ? { failedStores: failures } : {}) }, 200);
378
+ return jsonResponse({ ok: true, ...trace, stores: names, registeredStores: registeredStoreCount(deps), ...(resolution ? resolution.universe ? { resolvedFrom: 'ledger' as const, runUniverse: resolution.universe } : { runUniverse: 'not-found' as const, checked: resolution.checked } : {}), ...(failures.length ? { failedStores: failures } : {}) }, 200);
328
379
  } finally { closeStores(stores, deps); }
329
380
  }
330
381