elanous 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/README.md +7 -6
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
  9. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
  19. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
  93. package/apps/pwa/out/approvals/index.html +1 -1
  94. package/apps/pwa/out/approvals/index.txt +29 -27
  95. package/apps/pwa/out/autopilot/index.html +1 -1
  96. package/apps/pwa/out/autopilot/index.txt +29 -27
  97. package/apps/pwa/out/board/index.html +1 -1
  98. package/apps/pwa/out/board/index.txt +29 -27
  99. package/apps/pwa/out/botlab/index.html +1 -1
  100. package/apps/pwa/out/botlab/index.txt +26 -24
  101. package/apps/pwa/out/bots/index.html +1 -1
  102. package/apps/pwa/out/bots/index.txt +26 -24
  103. package/apps/pwa/out/chat/index.html +1 -1
  104. package/apps/pwa/out/chat/index.txt +26 -24
  105. package/apps/pwa/out/control/index.html +1 -1
  106. package/apps/pwa/out/control/index.txt +29 -27
  107. package/apps/pwa/out/design-check/index.html +1 -1
  108. package/apps/pwa/out/design-check/index.txt +29 -27
  109. package/apps/pwa/out/editor/index.html +1 -1
  110. package/apps/pwa/out/editor/index.txt +29 -27
  111. package/apps/pwa/out/healthz +1 -1
  112. package/apps/pwa/out/icons/elanous-16.png +0 -0
  113. package/apps/pwa/out/icons/elanous-180.png +0 -0
  114. package/apps/pwa/out/icons/elanous-192.png +0 -0
  115. package/apps/pwa/out/icons/elanous-32.png +0 -0
  116. package/apps/pwa/out/icons/elanous-512.png +0 -0
  117. package/apps/pwa/out/index.html +1 -1
  118. package/apps/pwa/out/index.txt +26 -24
  119. package/apps/pwa/out/intake/index.html +1 -1
  120. package/apps/pwa/out/intake/index.txt +29 -27
  121. package/apps/pwa/out/live/index.html +1 -1
  122. package/apps/pwa/out/live/index.txt +29 -27
  123. package/apps/pwa/out/manifest.webmanifest +5 -2
  124. package/apps/pwa/out/market/index.html +1 -1
  125. package/apps/pwa/out/market/index.txt +26 -24
  126. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  127. package/apps/pwa/out/missions/placeholder/index.txt +26 -24
  128. package/apps/pwa/out/morning/index.html +1 -1
  129. package/apps/pwa/out/morning/index.txt +29 -27
  130. package/apps/pwa/out/observatory/index.html +1 -1
  131. package/apps/pwa/out/observatory/index.txt +29 -27
  132. package/apps/pwa/out/reflection/index.html +1 -1
  133. package/apps/pwa/out/reflection/index.txt +27 -25
  134. package/apps/pwa/out/scheduler/index.html +1 -1
  135. package/apps/pwa/out/scheduler/index.txt +29 -27
  136. package/apps/pwa/out/sessions/index.html +1 -1
  137. package/apps/pwa/out/sessions/index.txt +26 -24
  138. package/apps/pwa/out/settings/devices/index.html +1 -1
  139. package/apps/pwa/out/settings/devices/index.txt +29 -27
  140. package/apps/pwa/out/settings/index.html +1 -1
  141. package/apps/pwa/out/settings/index.txt +29 -27
  142. package/apps/pwa/out/setup/done/index.html +1 -1
  143. package/apps/pwa/out/setup/done/index.txt +29 -27
  144. package/apps/pwa/out/setup/index.html +1 -1
  145. package/apps/pwa/out/setup/index.txt +29 -27
  146. package/apps/pwa/out/share/index.html +1 -1
  147. package/apps/pwa/out/share/index.txt +29 -27
  148. package/apps/pwa/out/showroom/index.html +1 -1
  149. package/apps/pwa/out/showroom/index.txt +26 -24
  150. package/apps/pwa/out/sw-strategies.js +44 -0
  151. package/apps/pwa/out/sw.js +6 -44
  152. package/apps/pwa/out/tasks/index.html +1 -1
  153. package/apps/pwa/out/tasks/index.txt +29 -27
  154. package/apps/pwa/out/term/index.html +1 -1
  155. package/apps/pwa/out/term/index.txt +29 -27
  156. package/apps/pwa/out/trace/index.html +1 -1
  157. package/apps/pwa/out/trace/index.txt +29 -27
  158. package/apps/pwa/out/vault/index.html +1 -1
  159. package/apps/pwa/out/vault/index.txt +29 -27
  160. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  161. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
  162. package/apps/pwa/out/workflows/index.html +1 -1
  163. package/apps/pwa/out/workflows/index.txt +29 -27
  164. package/apps/pwa/out/workspace/index.html +1 -1
  165. package/apps/pwa/out/workspace/index.txt +29 -27
  166. package/apps/pwa/out/worktrees/index.html +1 -1
  167. package/apps/pwa/out/worktrees/index.txt +29 -27
  168. package/catalog/resources.yaml +6 -6
  169. package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
  170. package/graphs/landing/landing-heal.yaml +1 -1
  171. package/graphs/release/recipes.yaml +11 -1
  172. package/graphs/release/release-loop.yaml +9 -2
  173. package/graphs/video/README.md +1 -0
  174. package/graphs/video/explainer-line.yaml +58 -0
  175. package/package.json +3 -1
  176. package/plugins/card-followup/README.md +33 -0
  177. package/plugins/card-followup/examples/card-sample.json +7 -0
  178. package/plugins/card-followup/graphs/card-followup.yaml +16 -0
  179. package/plugins/card-followup/graphs/recipes.yaml +12 -0
  180. package/plugins/card-followup/graphs/run-step.ts +172 -0
  181. package/plugins/card-followup/plugin.json +25 -0
  182. package/plugins/geo-check/README.md +13 -0
  183. package/plugins/geo-check/examples/brand-sample.json +8 -0
  184. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  185. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  186. package/plugins/geo-check/graphs/run-step.ts +177 -0
  187. package/plugins/geo-check/plugin.json +24 -0
  188. package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
  189. package/plugins/job-coach/README.md +42 -0
  190. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  191. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  192. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  193. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  194. package/plugins/job-coach/graphs/run-step.ts +211 -17
  195. package/plugins/job-coach/plugin.json +16 -3
  196. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  197. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  198. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  199. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  200. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  201. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  202. package/scripts/backup/elanous-backup.sh +4 -1
  203. package/scripts/build-docs-site.ts +4 -3
  204. package/scripts/coord-channel-watch.sh +23 -4
  205. package/scripts/coord-post.sh +49 -12
  206. package/scripts/coord-tracks.json +5 -5
  207. package/scripts/daemon-port-baseline.txt +1 -2
  208. package/scripts/eval-bot-vs-grok.ts +79 -0
  209. package/scripts/eval-grok-live-baseline.ts +36 -0
  210. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  211. package/scripts/fleet/node-setup.sh +46 -7
  212. package/scripts/landing-heal/must-fix-match.ts +26 -0
  213. package/scripts/landing-heal/run-mission.ts +114 -20
  214. package/scripts/lib/android-test-device.ts +32 -0
  215. package/scripts/lib/front-route-measurement.ts +131 -0
  216. package/scripts/lib/tui-sim-keys.ts +42 -0
  217. package/scripts/measure-front-route-classifier.ts +56 -0
  218. package/scripts/media/ocr-leak-scan.sh +21 -0
  219. package/scripts/public-export.ts +2 -3
  220. package/scripts/public-import-pr.ts +80 -6
  221. package/scripts/publish-internal-dist.ts +2 -3
  222. package/scripts/red-sweep-diff.ts +1 -1
  223. package/scripts/release-loop/auto-approve-node.ts +72 -0
  224. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  225. package/scripts/release-loop/gate-diff.ts +29 -0
  226. package/scripts/release-loop/gate-node.ts +366 -45
  227. package/scripts/release-loop/publish-node.ts +7 -3
  228. package/scripts/release-loop/release-docs.ts +20 -13
  229. package/scripts/release-loop/shard-plan.ts +47 -0
  230. package/scripts/release-loop/test-durations.ts +63 -0
  231. package/scripts/release-loop/upgrade-node.ts +3 -2
  232. package/scripts/test-audit-coverage-per-file.sh +16 -0
  233. package/scripts/test-audit-meaningless.ts +88 -0
  234. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  235. package/scripts/test-deterministic.ts +27 -5
  236. package/scripts/tui-sim.ts +59 -14
  237. package/scripts/ux/bump-installed.sh +20 -0
  238. package/scripts/ux/lib/cdp.ts +72 -0
  239. package/scripts/ux/record-page.ts +53 -0
  240. package/skills/explainer-video/SKILL.md +117 -0
  241. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  242. package/skills/explainer-video/engine/build.mjs +431 -0
  243. package/skills/explainer-video/engine/reel.mjs +128 -0
  244. package/skills/explainer-video/engine/reel.sh +8 -0
  245. package/skills/explainer-video/engine/vo.ts +79 -0
  246. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  247. package/src/agent/nested-agent-env.ts +1 -0
  248. package/src/agent-mission/driver.ts +95 -16
  249. package/src/agent-mission/mission-cli.ts +6 -0
  250. package/src/agent-mission/resource-ladder.ts +73 -25
  251. package/src/auth/acp-token.ts +16 -0
  252. package/src/auth/github-app-token.ts +112 -0
  253. package/src/autopilot/gh-rest-fallback.ts +177 -0
  254. package/src/autopilot/mission-origin.ts +20 -4
  255. package/src/autopilot/mission-phase-track.ts +11 -1
  256. package/src/autopilot/pr-manager.ts +51 -4
  257. package/src/autopilot/track-agent.ts +3 -3
  258. package/src/autopilot/track-courier.ts +2 -2
  259. package/src/boot/acp-server.ts +2 -16
  260. package/src/boot/daemon-tools/web-search.ts +2 -1
  261. package/src/boot/ws-bridge.ts +11 -0
  262. package/src/chat/index.ts +37 -8
  263. package/src/chat/log-scroll-key.ts +12 -0
  264. package/src/chat/submit-trace.ts +30 -0
  265. package/src/cli/agent-cli.ts +7 -2
  266. package/src/cli/decisions-cli.ts +94 -0
  267. package/src/cli/doctor-cli.ts +40 -54
  268. package/src/cli/doctor-readiness.ts +16 -24
  269. package/src/cli/intake-cli.ts +1 -1
  270. package/src/cli/mcp-call.ts +9 -0
  271. package/src/cli/mcp-gateway-cli.ts +7 -3
  272. package/src/cli/nexus-ios-bind.ts +25 -8
  273. package/src/cli/phone-link.ts +117 -0
  274. package/src/cli/plugin-cli.ts +136 -11
  275. package/src/cli/pr-lineage.ts +2 -1
  276. package/src/cli/private-ledger-commands.ts +31 -0
  277. package/src/cli/pwa-start.ts +3 -0
  278. package/src/cli/release-cli.ts +97 -7
  279. package/src/cli/self-update.ts +7 -4
  280. package/src/cli/start-cli.ts +33 -14
  281. package/src/dashboard/acp-boot.ts +3 -0
  282. package/src/dashboard/index.ts +62 -13
  283. package/src/dashboard/input/no-provider-reply.ts +13 -0
  284. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  285. package/src/dashboard/stale-install-check.ts +77 -0
  286. package/src/decisions/decision-ledger.ts +404 -0
  287. package/src/domains/outbound-alert.ts +13 -7
  288. package/src/domains/telegram-kind-route.ts +21 -0
  289. package/src/execution-loop/dispatch-task.ts +139 -0
  290. package/src/execution-loop/gate-decision-summary.ts +29 -0
  291. package/src/execution-loop/gates.ts +197 -0
  292. package/src/field/field-media.ts +245 -0
  293. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  294. package/src/graph-kinds/plugin-nodes.ts +10 -2
  295. package/src/graph-runner/runner.ts +35 -5
  296. package/src/grounding/token.ts +12 -6
  297. package/src/harness/harness-cli-command.ts +68 -9
  298. package/src/harness/harness-pod-dispatch.ts +8 -2
  299. package/src/harness/harness-stop.ts +147 -18
  300. package/src/harness/self-send-decide.ts +26 -17
  301. package/src/harness/self-send-target.ts +22 -1
  302. package/src/harness/worktree-branch-prefix.ts +4 -3
  303. package/src/index.ts +50 -13
  304. package/src/instance/resolve.ts +6 -0
  305. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  306. package/src/intake-plane/digest.ts +11 -2
  307. package/src/live/detail-switch.ts +8 -3
  308. package/src/llm/usable-llm.ts +32 -0
  309. package/src/loops/registry.ts +27 -5
  310. package/src/market/publish.ts +59 -3
  311. package/src/native-tool-catalog.ts +2 -2
  312. package/src/nexus/api/field-uploads.ts +143 -0
  313. package/src/nexus/api/graph-approvals.ts +174 -0
  314. package/src/nexus/api/harness-api.ts +219 -5
  315. package/src/nexus/api/http-server.ts +69 -6
  316. package/src/nexus/api/log-fabric.ts +10 -1
  317. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  318. package/src/nexus/api/merge-approvals.ts +2 -1
  319. package/src/nexus/api/meta-api.ts +14 -0
  320. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  321. package/src/nexus/api/plugins-market.ts +87 -3
  322. package/src/nexus/api/pod-credential-api.ts +68 -14
  323. package/src/nexus/api/public-routes.ts +7 -0
  324. package/src/nexus/api/trace.ts +57 -6
  325. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  326. package/src/nexus/index.ts +33 -32
  327. package/src/nexus/outbound/router.ts +1 -1
  328. package/src/notify/llm-budget-alert.ts +119 -0
  329. package/src/onboarding.ts +25 -2
  330. package/src/plugins/core/host.ts +14 -0
  331. package/src/plugins/install/market-fetch.ts +4 -3
  332. package/src/plugins/install/plugin-credentials.ts +106 -0
  333. package/src/plugins/install/plugin-install.ts +163 -32
  334. package/src/plugins/maker/plugin-maker.ts +208 -0
  335. package/src/prompt-library/universal-preamble.ts +8 -2
  336. package/src/relay/channel.ts +184 -0
  337. package/src/relay/mux.ts +182 -0
  338. package/src/relay/test-vectors.json +29 -0
  339. package/src/release-loop/checklist.ts +217 -0
  340. package/src/release-loop/release-note.ts +6 -3
  341. package/src/seat-address/seat-address.ts +34 -0
  342. package/src/self-dev/dev-cli.ts +40 -20
  343. package/src/self-dev/dev-pipeline.ts +77 -1
  344. package/src/self-implement/gate-baseline.ts +3 -1
  345. package/src/self-implement/goal-author.ts +18 -1
  346. package/src/self-implement/host-regate.ts +6 -2
  347. package/src/self-implement/orchestrator.ts +7 -2
  348. package/src/self-implement/run-ledger-compact.ts +1 -1
  349. package/src/self-implement/seams.ts +4 -2
  350. package/src/session/index.ts +23 -10
  351. package/src/skills/tools/dev-harness.ts +42 -0
  352. package/src/steward/steward-cards.ts +45 -0
  353. package/src/steward/triage.ts +50 -14
  354. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  355. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  356. package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  358. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  359. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  360. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  361. package/src/telegram-report.ts +12 -6
  362. package/src/telegram.ts +108 -1
  363. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  364. package/src/tui.ts +3 -2
  365. package/src/user-config.ts +32 -2
  366. package/src/util/spawn-sync-output.ts +15 -0
  367. package/src/version/code-revision.ts +1 -1
  368. package/src/version/packed-revision.json +1 -1
  369. package/src/video-pipeline/recipes/explainer.ts +213 -0
  370. package/src/video-pipeline/walk-line.ts +3 -2
  371. package/src/web-search/free.ts +77 -0
  372. package/src/web-search/index.ts +2 -0
  373. package/src/workflow-runtime/deps-bridge.ts +4 -3
  374. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  375. package/src/workflow-runtime/types.ts +2 -1
  376. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  388. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  454. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  455. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
@@ -0,0 +1,79 @@
1
+ // Explainer VO — one voice file per beat plus per-character times; the times drive caption & highlight timing.
2
+ // Usage: ELEVENLABS_API_KEY=… bun vo.ts <project>/script.json (ElevenLabs /with-timestamps — exact word times)
3
+ // bun vo.ts <project>/script.json (no key → macOS `say`, times spread evenly — a draft voice)
4
+ // Writes source/vo/<key>.mp3 ⊕ <key>.json (alignment ⊕ text hash ⊕ engine). Skips a beat whose hash is unchanged. Never prints the key.
5
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { createHash } from 'node:crypto';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { join, dirname, resolve } from 'node:path';
9
+
10
+ const KEY = process.env.ELEVENLABS_API_KEY?.trim();
11
+ const scriptPath = resolve(process.argv[2] ?? 'script.json');
12
+ const script = JSON.parse(readFileSync(scriptPath, 'utf8'));
13
+ const dir = join(dirname(scriptPath), 'source', 'vo');
14
+ mkdirSync(dir, { recursive: true });
15
+
16
+ export function beatKeys(s: any): { key: string; vo: string }[] {
17
+ const out: { key: string; vo: string }[] = [];
18
+ s.intro.beats.forEach((b: any, i: number) => out.push({ key: `i-${i}`, vo: b.vo }));
19
+ s.chapters.forEach((c: any, ci: number) => c.beats.forEach((b: any, i: number) => out.push({ key: `c${ci + 1}-${i}`, vo: b.vo })));
20
+ s.outro.beats.forEach((b: any, i: number) => out.push({ key: `o-${i}`, vo: b.vo }));
21
+ return out;
22
+ }
23
+
24
+ type Alignment = { characters: string[]; character_start_times_seconds: number[]; character_end_times_seconds: number[] };
25
+
26
+ async function elevenlabs(vo: string): Promise<{ mp3: Buffer; alignment: Alignment }> {
27
+ const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${script.voice.id}/with-timestamps?output_format=mp3_44100_192`, {
28
+ method: 'POST',
29
+ headers: { 'xi-api-key': KEY!, 'content-type': 'application/json' },
30
+ body: JSON.stringify({ text: vo, model_id: script.voice.model, language_code: 'ko' }),
31
+ });
32
+ if (!res.ok) throw new Error(`ElevenLabs HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
33
+ const data = await res.json() as { audio_base64: string; alignment: Alignment };
34
+ return { mp3: Buffer.from(data.audio_base64, 'base64'), alignment: data.alignment };
35
+ }
36
+
37
+ /** Keyless draft voice: macOS `say` → mp3 · character times spread in proportion over the measured length. */
38
+ function sayVoice(): string {
39
+ const want = process.env.EXPLAINER_SAY_VOICE;
40
+ if (want) return want;
41
+ const list = spawnSync('say', ['-v', '?'], { encoding: 'utf8' });
42
+ if (list.status !== 0) throw new Error('no ELEVENLABS_API_KEY and `say` is unavailable (macOS only) — set the key via the plugin connector');
43
+ const ko = list.stdout.split('\n').filter((l) => /\bko_KR\b/.test(l)).map((l) => l.split(/\s{2,}|\s+\(/)[0].trim());
44
+ const pick = ko.find((v) => /^Yuna/.test(v)) ?? ko[0];
45
+ if (!pick) throw new Error('no Korean `say` voice installed — System Settings › Accessibility › Spoken Content, or set ELEVENLABS_API_KEY');
46
+ return pick;
47
+ }
48
+ function say(vo: string, key: string): { mp3: Buffer; alignment: Alignment } {
49
+ const voice = sayVoice();
50
+ const aiff = join(dir, `${key}.tmp.aiff`), mp3 = join(dir, `${key}.tmp.mp3`);
51
+ const r = spawnSync('say', ['-v', voice, '-o', aiff, vo], { encoding: 'utf8' });
52
+ if (r.status !== 0) throw new Error(`say failed: ${r.stderr.slice(0, 200)}`);
53
+ const f = spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', aiff, '-ar', '44100', '-b:a', '192k', mp3], { encoding: 'utf8' });
54
+ if (f.status !== 0) throw new Error(`ffmpeg failed: ${f.stderr.slice(0, 200)}`);
55
+ const p = spawnSync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', mp3], { encoding: 'utf8' });
56
+ const dur = Number(p.stdout.trim());
57
+ if (!(dur > 0)) throw new Error('ffprobe could not measure the say output');
58
+ const chars = [...vo];
59
+ const weight = chars.map((c) => (/\s/.test(c) ? 0.3 : /[,.·!?]/.test(c) ? 0.8 : 1));
60
+ const total = weight.reduce((a, b) => a + b, 0);
61
+ const starts: number[] = [], ends: number[] = [];
62
+ let t = 0;
63
+ for (const w of weight) { starts.push(+t.toFixed(3)); t += (w / total) * dur; ends.push(+t.toFixed(3)); }
64
+ const out = readFileSync(mp3);
65
+ rmSync(aiff, { force: true }); rmSync(mp3, { force: true });
66
+ return { mp3: out, alignment: { characters: chars, character_start_times_seconds: starts, character_end_times_seconds: ends } };
67
+ }
68
+
69
+ const engine = KEY ? 'elevenlabs' : 'say';
70
+ if (!KEY) console.log('no ELEVENLABS_API_KEY — draft voice with macOS `say` (caption times are estimates)');
71
+ for (const { key, vo } of beatKeys(script)) {
72
+ const hash = createHash('sha256').update(`${engine === 'say' ? 'say|' : ''}${script.voice.id}|${script.voice.model}|${vo}`).digest('hex').slice(0, 16);
73
+ const jf = join(dir, `${key}.json`);
74
+ if (existsSync(jf) && JSON.parse(readFileSync(jf, 'utf8')).hash === hash) { console.log(`${key} cached`); continue; }
75
+ const { mp3, alignment } = KEY ? await elevenlabs(vo) : say(vo, key);
76
+ writeFileSync(join(dir, `${key}.mp3`), mp3);
77
+ writeFileSync(jf, JSON.stringify({ hash, engine, vo, alignment }));
78
+ console.log(`${key} ${(alignment.character_end_times_seconds.at(-1) ?? 0).toFixed(2)}s ${engine} ${vo}`);
79
+ }
@@ -0,0 +1,155 @@
1
+ {
2
+ "title": "엘라누스 0.2.5 — 다섯 가지 한 번에",
3
+ "voice": {
4
+ "id": "ksaI0TCD9BstzEzlxj4q",
5
+ "name": "Seulki",
6
+ "model": "eleven_v3"
7
+ },
8
+ "sources": "docs/marketing (LAUNCH-0.2.5 초안) · PR #21849 #21887 #21878 #21855 #22054 #21908 #21995 #22001 — 전부 컷 e24cb6e 안 MERGED",
9
+ "intro": {
10
+ "beats": [
11
+ {
12
+ "vo": "엘라누스 0.2.5, 다섯 가지로 정리해 드립니다.",
13
+ "cap": "엘라누스 0.2.5, **다섯 가지**로 정리합니다"
14
+ }
15
+ ]
16
+ },
17
+ "chapters": [
18
+ {
19
+ "title": "미션 전 플러그인 자기 설치",
20
+ "sub": "모자란 것을 먼저 따진다",
21
+ "diagram": "mission",
22
+ "beats": [
23
+ {
24
+ "vo": "첫 번째는 플러그인 자기 설치입니다.",
25
+ "cap": "첫 번째, **플러그인 자기 설치**"
26
+ },
27
+ {
28
+ "vo": "미션을 보내기 전에, 엘라누스가 무엇이 모자란지 먼저 따집니다.",
29
+ "cap": "보내기 전에 **무엇이 모자란지** 먼저 따진다"
30
+ },
31
+ {
32
+ "vo": "실제 첫 판에서는 플러그인 이름을 주지 않았는데, 엘라누스 베이직스를 골라 코딩 에이전트 화면 안에서 설치했습니다.",
33
+ "cap": "이름을 안 줬는데 **elanous-basics**를 골라 설치",
34
+ "hl": [
35
+ [
36
+ "elanous-basics",
37
+ "베이직스"
38
+ ]
39
+ ]
40
+ },
41
+ {
42
+ "vo": "사람이 챙겨 주던 도구를, 에이전트가 먼저 챙기는 거죠.",
43
+ "cap": "도구를 **에이전트가 먼저** 챙긴다"
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "title": "plugin add",
49
+ "sub": "이름@마켓 한 줄",
50
+ "diagram": "terminal",
51
+ "beats": [
52
+ {
53
+ "vo": "두 번째는 플러그인 애드 명령입니다.",
54
+ "cap": "두 번째, **plugin add**",
55
+ "hl": [
56
+ [
57
+ "plugin add",
58
+ "애드"
59
+ ]
60
+ ]
61
+ },
62
+ {
63
+ "vo": "이름과 마켓만 적으면 됩니다.",
64
+ "cap": "**이름@마켓**만 적는다",
65
+ "hl": [
66
+ [
67
+ "이름@마켓",
68
+ "마켓"
69
+ ]
70
+ ]
71
+ },
72
+ {
73
+ "vo": "색인 서명과 패키지 해시를 확인한 뒤에만 설치합니다.",
74
+ "cap": "**서명**과 **해시**를 확인한 뒤에만 설치"
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "title": "편집기 플러그인 노드",
80
+ "sub": "입력 폼이 저절로",
81
+ "diagram": "nodes",
82
+ "beats": [
83
+ {
84
+ "vo": "세 번째는 워크플로 편집기의 플러그인 노드입니다.",
85
+ "cap": "세 번째, **플러그인 노드**"
86
+ },
87
+ {
88
+ "vo": "플러그인이 노드를 더하고,",
89
+ "cap": "플러그인이 **노드를 더하고**"
90
+ },
91
+ {
92
+ "vo": "입력 폼은 노드의 입력 정의에서 저절로 만들어집니다.",
93
+ "cap": "입력 폼은 **저절로** 만들어진다"
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "title": "플러그인별 자격 키",
99
+ "sub": "키는 그 플러그인에게만",
100
+ "diagram": "keys",
101
+ "beats": [
102
+ {
103
+ "vo": "네 번째는 플러그인별 자격 키입니다.",
104
+ "cap": "네 번째, **플러그인별 자격 키**"
105
+ },
106
+ {
107
+ "vo": "키를 넣을 문이 생겼고,",
108
+ "cap": "키를 **넣을 문**이 생겼고"
109
+ },
110
+ {
111
+ "vo": "그 키는 그 플러그인에게만 전달됩니다.",
112
+ "cap": "키는 **그 플러그인에게만** 간다"
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "title": "승인 화면",
118
+ "sub": "웹 앱에서 승인·거절",
119
+ "diagram": "approve",
120
+ "beats": [
121
+ {
122
+ "vo": "다섯 번째는 웹 앱의 승인 화면입니다.",
123
+ "cap": "다섯 번째, **승인 화면**"
124
+ },
125
+ {
126
+ "vo": "릴리스나 공지처럼 사람의 승인에서 멈춘 런을,",
127
+ "cap": "사람의 승인에서 **멈춘 런**을"
128
+ },
129
+ {
130
+ "vo": "웹 앱에서 바로 승인하거나 거절합니다.",
131
+ "cap": "웹 앱에서 바로 **승인·거절**"
132
+ }
133
+ ]
134
+ }
135
+ ],
136
+ "outro": {
137
+ "beats": [
138
+ {
139
+ "vo": "정리하면, 이번 판의 핵심은 이것입니다.",
140
+ "cap": "이번 판의 **핵심**"
141
+ },
142
+ {
143
+ "vo": "필요한 도구는 에이전트가 스스로 챙기고, 사람은 승인합니다.",
144
+ "cap": "도구는 **에이전트가 스스로**, 사람은 **승인**"
145
+ }
146
+ ],
147
+ "cta": {
148
+ "line": "Elanous by Elanvital AI",
149
+ "links": [
150
+ "elanous.ai",
151
+ "github.com/ElanvitalAI/elanous"
152
+ ]
153
+ }
154
+ }
155
+ }
@@ -1,6 +1,7 @@
1
1
  /** Parent-only markers that make a spawned coding agent reject nested execution. */
2
2
  export const NESTED_AGENT_ENV_BLOCKLIST = new Set<string>([
3
3
  'CLAUDECODE',
4
+ 'CLAUDE_CODE_CHILD_SESSION',
4
5
  'ELANOUS_SESSION_ID',
5
6
  'ELANOUS_UNDO_REF',
6
7
  'ELANOUS_GUARDIAN',
@@ -9,7 +9,7 @@
9
9
  // 재사용: startPty(Bun 네이티브 PTY)·createWorktree·streamLLM·worktreeHasChanges·commitWorktree.
10
10
  import { execFileSync, spawnSync, spawn as spawnChild } from 'node:child_process';
11
11
  import { homedir } from 'node:os';
12
- import { existsSync, writeFileSync, mkdirSync, readdirSync, readFileSync, statSync, readlinkSync, realpathSync, lstatSync } from 'node:fs';
12
+ import { existsSync, writeFileSync, mkdirSync, readdirSync, readFileSync, statSync, readlinkSync, realpathSync, lstatSync, unlinkSync } from 'node:fs';
13
13
  import { createHash } from 'node:crypto';
14
14
  import { join, relative, resolve, isAbsolute, sep } from 'node:path';
15
15
  import { startPty, mintPtyId, onPtyEvent, type PtyHandle, type StartOpts } from '../pty-shell/registry.js';
@@ -34,8 +34,10 @@ import { runWithControlObserve } from '../capture/control-observe-adapter.js';
34
34
  import { buildMissionProvision, sanitizeForPtyInput, type ProvisionRequest, type ProvisionResult } from './provision.js';
35
35
  import { checkClaudeSubscription } from './claude-subscription.js';
36
36
  import { handoffMission, waitForHumanLogin, type HandoffOperations } from './handoff.js';
37
+ import { dispatchOpenPullRequest } from '../tool-runtime/git-pr-runtime.js';
37
38
  import { runClaudeHeadless } from './claude-headless.js';
38
39
  import { installPluginInsideAgent, type PluginRequest, type PluginDeps } from './in-agent-plugin.js';
40
+ import { planMissionResources, type ResourceLadderDeps, type ResourcePlan } from './resource-ladder.js';
39
41
  import { ensureRunId } from '../harness/harness-space.js';
40
42
  import { recordHarnessWorktreeProvenance } from '../harness/harness-worktree-add.js';
41
43
  import { resolveInstanceName } from '../instance-identity.js';
@@ -459,6 +461,10 @@ export interface AgentMissionSpec {
459
461
  headless?: boolean;
460
462
  /** Install in the agent's own UI before sending the mission. */
461
463
  plugin?: Pick<PluginRequest, 'plugin' | 'marketplace'>;
464
+ /** Pre-mission resource judgment; off skips it. */
465
+ resources?: 'on' | 'off';
466
+ /** Only the caller's explicit backend selection, not the resolved default. */
467
+ backendExplicit?: string;
462
468
  /** Explicit backend sequence; transitions use the same handoff coordinator as brain decisions. */
463
469
  chain?: readonly ('codex' | 'claude' | 'elanous')[];
464
470
  /**
@@ -496,7 +502,8 @@ export interface AgentMissionResult {
496
502
  ok: boolean;
497
503
  worktree: string;
498
504
  branch: string | null;
499
- reason?: 'aborted' | 'outside-write';
505
+ reason?: 'aborted' | 'outside-write' | 'origin-not-github';
506
+ pr?: 'skipped';
500
507
  paths?: string[];
501
508
  rounds: number;
502
509
  evidencePath: string | null;
@@ -526,6 +533,7 @@ export interface AgentMissionDeps {
526
533
  runClaudeHeadless?: typeof runClaudeHeadless;
527
534
  checkEvidence?: typeof checkEvidence;
528
535
  commitWorktree?: typeof commitWorktree;
536
+ dispatchOpenPullRequest?: typeof dispatchOpenPullRequest;
529
537
  terminateProcessTree?: typeof terminateMissionProcessTree;
530
538
  repoStatus?: (repo: string) => string | Promise<string>;
531
539
  emitDecision?: typeof emitDecision;
@@ -546,6 +554,8 @@ export interface AgentMissionDeps {
546
554
  initialTsc?: ReturnType<typeof collectTscDiagnostics> | null;
547
555
  installPlugin?: typeof installPluginInsideAgent;
548
556
  pluginDeps?: PluginDeps;
557
+ resourceLadder?: ResourceLadderDeps;
558
+ planMissionResources?: typeof planMissionResources;
549
559
  }
550
560
 
551
561
  const DEFAULT_OMNI = `${process.env.HOME}/.claude/skills/omni-crawl/scripts/main.ts`;
@@ -1644,16 +1654,55 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1644
1654
  screen = await (deps.awaitMission ? deps.awaitMission(trusted) : trusted);
1645
1655
  }
1646
1656
 
1647
- if (spec.plugin) {
1648
- const plugin = await (deps.installPlugin ?? installPluginInsideAgent)(
1649
- { agent: backend.name as 'codex' | 'claude', ...spec.plugin }, h,
1650
- { ...(backend.handleTrust ? { handleTrust: backend.handleTrust } : {}), ...deps.pluginDeps, write: drive },
1651
- );
1652
- if (plugin.outcome !== 'installed') {
1653
- stopLive();
1654
- try { h.kill(); } catch { /* already exited */ }
1655
- return { ok: false, worktree: wt.path, branch: wt.branch, rounds: 0, evidencePath: null,
1656
- committed: false, usedOmniCrawl: false, detail: `플러그인 설치 ESCALATE (${plugin.reason}) — 미션은 전송하지 않았다` };
1657
+ let resourcePlan: ResourcePlan | undefined;
1658
+ // Only an explicit `on` runs the ladder (the CLI passes `on` by default). Library callers and fake-PTY
1659
+ // tests that leave it unset keep the pre-ladder behaviour — 09-29 #21849 made them wait on a real LLM.
1660
+ if (spec.resources === 'on') {
1661
+ let timer: ReturnType<typeof setTimeout> | undefined;
1662
+ try {
1663
+ resourcePlan = await Promise.race([
1664
+ (deps.planMissionResources ?? planMissionResources)(spec.mission, deps.resourceLadder ?? {}, {
1665
+ backend: spec.backendExplicit,
1666
+ plugin: spec.plugin ? `${spec.plugin.plugin}@${spec.plugin.marketplace}` : undefined,
1667
+ resources: spec.resources,
1668
+ deadlineMs: 20_000,
1669
+ }),
1670
+ new Promise<never>((_resolve, reject) => { timer = setTimeout(() => reject(new Error('resource ladder timeout (20s)')), 20_000); }),
1671
+ ]);
1672
+ debug.log('agent-mission.resources', 'planned', {
1673
+ have: resourcePlan.have.length, gaps: resourcePlan.gaps.length,
1674
+ suggestedPlugin: resourcePlan.plugin?.plugin, suggestedBackend: resourcePlan.backend?.name,
1675
+ executedBackend: backend.name,
1676
+ });
1677
+ } catch (error) {
1678
+ debug.log('agent-mission.resources', 'plan-failed', { reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
1679
+ } finally { if (timer) clearTimeout(timer); }
1680
+ }
1681
+ const suggestedPlugin = resourcePlan?.plugin?.source === 'official-index' && !spec.plugin
1682
+ && !spec.headless && (backend.name === 'codex' || backend.name === 'claude')
1683
+ && resourcePlan.plugin.marketplace === 'elanous'
1684
+ && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(resourcePlan.plugin.plugin)
1685
+ ? { plugin: resourcePlan.plugin.plugin, marketplace: resourcePlan.plugin.marketplace } : undefined;
1686
+ const requestedPlugin = spec.plugin ?? suggestedPlugin;
1687
+ let installedPlugin: typeof requestedPlugin;
1688
+ if (requestedPlugin) {
1689
+ try {
1690
+ const plugin = await (deps.installPlugin ?? installPluginInsideAgent)(
1691
+ { agent: backend.name as 'codex' | 'claude', ...requestedPlugin }, h,
1692
+ { ...(backend.handleTrust ? { handleTrust: backend.handleTrust } : {}), ...deps.pluginDeps, write: drive },
1693
+ );
1694
+ if (plugin.outcome !== 'installed') {
1695
+ if (spec.plugin) {
1696
+ stopLive();
1697
+ try { h.kill(); } catch { /* already exited */ }
1698
+ return { ok: false, worktree: wt.path, branch: wt.branch, rounds: 0, evidencePath: null,
1699
+ committed: false, usedOmniCrawl: false, detail: `플러그인 설치 ESCALATE (${plugin.reason}) — 미션은 전송하지 않았다` };
1700
+ }
1701
+ debug.log('agent-mission.resources', 'install-failed', { plugin: requestedPlugin.plugin, reason: plugin.reason }, { level: 'warn' });
1702
+ } else installedPlugin = requestedPlugin;
1703
+ } catch (error) {
1704
+ if (spec.plugin) throw error;
1705
+ debug.log('agent-mission.resources', 'install-failed', { plugin: requestedPlugin.plugin, reason: 'installer-error' }, { level: 'warn' });
1657
1706
  }
1658
1707
  }
1659
1708
 
@@ -1683,7 +1732,15 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1683
1732
  else debug.log('agent-mission', 'memory', { injected: false });
1684
1733
  }
1685
1734
 
1686
- if (spec.plugin) missionText = `${missionText}\n\n설치된 ${spec.plugin.plugin} 플러그인에서 실제 스킬을 확인하고 ${spec.plugin.plugin}:<skill> 형식으로 해당 스킬을 사용하라.`;
1735
+ if (installedPlugin) missionText = `${missionText}\n\n설치된 ${installedPlugin.plugin} 플러그인에서 실제 스킬을 확인하고 ${installedPlugin.plugin}:<skill> 형식으로 해당 스킬을 사용하라.`;
1736
+ if (resourcePlan) {
1737
+ const safeName = (name: string) => sanitizeForPtyInput(name, 80)
1738
+ .replace(/\b(?:bearer|secret|password|token|api.?key|authorization)\s*[:=]\s*\S+|(?:sk-[\w-]+|gh[pousr]_[\w-]+|github_pat_[\w-]+|AIza[\w-]+|xox[baprs]-[\w-]+)/gi, '[redacted]')
1739
+ .replace(/https?:\/\/\S+/gi, '[redacted]');
1740
+ const have = resourcePlan.have.slice(0, 5).map(safeName);
1741
+ const gaps = resourcePlan.gaps.slice(0, 5).map(gap => safeName(gap.need));
1742
+ missionText = `${missionText}\n\n[elanous 자원]\n보유: ${have.join(', ') || '없음'}\n설치: ${installedPlugin ? `${installedPlugin.plugin}@${installedPlugin.marketplace}` : '없음'}\n미충족: ${gaps.join(', ') || '없음'}`;
1743
+ }
1687
1744
  if (deps.isMissionAborted?.()) throw new Error('MISSION_ABORTED');
1688
1745
  // 미션 전송 — 인핸싱/기억으로 가공됐으면(멀티라인) 파일로 떨궈 read 지시(TUI 멀티라인 위험·verbatim 보존).
1689
1746
  // 원문 그대로면(단문) 타이핑.
@@ -1738,6 +1795,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1738
1795
  };
1739
1796
  let pendingHandoff: { to: 'codex' | 'claude'; mission: string; summary: string } | undefined;
1740
1797
  let gatedHandoff = false;
1798
+ let handoffPrSkipped = false;
1741
1799
  let transitionFailure: string | undefined;
1742
1800
  const handoffOps: HandoffOperations = {
1743
1801
  start: async (to, mission) => { pendingHandoff = { to, mission, summary: controllerSummary }; },
@@ -1765,11 +1823,30 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1765
1823
  if (spec.commit === false) throw new Error('elanous PR handoff requires commit authorization');
1766
1824
  const ev = (deps.checkEvidence ?? evidenceCheck)(worktree, spec.evidence);
1767
1825
  if (!ev.ok) throw new Error(`handoff evidence gate failed: ${ev.retry ?? 'missing evidence'}`);
1826
+ const promptFile = join(worktree, '.mission-prompt.md');
1827
+ let promptExisted = false;
1828
+ try { lstatSync(promptFile); promptExisted = true; } catch (error) {
1829
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
1830
+ }
1831
+ if (promptExisted) unlinkSync(promptFile);
1832
+ debug.log('agent-mission', 'handoff-prompt-file-removed', { existed: promptExisted });
1833
+ const stagedPrompt = execFileSync('git', ['ls-files', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' });
1834
+ if (stagedPrompt.trim()) {
1835
+ execFileSync('git', ['rm', '-f', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' });
1836
+ }
1837
+ if (execFileSync('git', ['ls-files', '--cached', '--', '.mission-prompt.md'], { cwd: worktree, encoding: 'utf8' }).trim()) {
1838
+ throw new Error('handoff prompt file remains in commit index');
1839
+ }
1768
1840
  const commit = (deps.commitWorktree ?? commitWorktree)(worktree, `chore(agent-mission): ${spec.branch} handoff`);
1769
1841
  if (!commit.ok) throw new Error(`handoff commit failed: ${commit.out}`);
1770
1842
  execFileSync('git', ['push', '-u', 'origin', `HEAD:${spec.branch}`], { cwd: worktree, encoding: 'utf8', timeout: 120_000 });
1771
- const { dispatchOpenPullRequest } = await import('../tool-runtime/git-pr-runtime.js');
1772
- dispatchOpenPullRequest({ title: `agent-mission: ${spec.branch}`, body: deps.originalMission ?? spec.mission, head: spec.branch, base: spec.base ?? 'main' }, { cwd: worktree });
1843
+ const pushUrls = execFileSync('git', ['remote', 'get-url', '--push', '--all', 'origin'], { cwd: worktree, encoding: 'utf8' }).trim().split('\n');
1844
+ if (!pushUrls.length || !pushUrls.every((url) => /^(?:https:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:)[^/]+\/[^/]+\/?$/i.test(url))) {
1845
+ handoffPrSkipped = true;
1846
+ debug.log('agent-mission', 'handoff-pr-skipped', { reason: 'origin-not-github' });
1847
+ } else {
1848
+ (deps.dispatchOpenPullRequest ?? dispatchOpenPullRequest)({ title: `agent-mission: ${spec.branch}`, body: deps.originalMission ?? spec.mission, head: spec.branch, base: spec.base ?? 'main' }, { cwd: worktree });
1849
+ }
1773
1850
  gatedHandoff = true;
1774
1851
  },
1775
1852
  ...deps.handoffOperations,
@@ -1879,7 +1956,9 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1879
1956
  if (gatedHandoff && control.termination.kind !== 'error') {
1880
1957
  stopLive();
1881
1958
  try { h.kill(); } catch { /* noop */ }
1882
- return { ok: true, worktree: wt.path, branch: wt.branch, rounds: control.steps, evidencePath: wt.path, committed: true, usedOmniCrawl: usedOmni, detail: '완료(증거 게이트·PR)' };
1959
+ return { ok: true, worktree: wt.path, branch: wt.branch, rounds: control.steps, evidencePath: wt.path, committed: true, usedOmniCrawl: usedOmni,
1960
+ ...(handoffPrSkipped ? { pr: 'skipped' as const, reason: 'origin-not-github' as const } : {}),
1961
+ detail: handoffPrSkipped ? '완료(증거 게이트·push·PR 생략)' : '완료(증거 게이트·PR)' };
1883
1962
  }
1884
1963
  const done = control.termination.kind === 'success';
1885
1964
  const round = control.steps;
@@ -18,6 +18,7 @@ export interface MissionCliOpts {
18
18
  chain?: string;
19
19
  headless?: boolean;
20
20
  plugin?: string;
21
+ resources?: 'on' | 'off';
21
22
  evidence?: string; // 'doc' | 'tsc' | 'test'
22
23
  docDir?: string;
23
24
  docGlob?: string;
@@ -61,6 +62,9 @@ export async function runAgentMissionCliCommand(
61
62
  if (opts.plugin !== undefined && (!plugin || !['codex', 'claude'].includes(agentBackend.name) || opts.headless)) {
62
63
  return { ok: false, message: '--plugin 은 codex/claude 대화형 backend 에서 <name>@<market> 형식이어야 합니다', exitCode: 1 };
63
64
  }
65
+ if (opts.resources !== undefined && opts.resources !== 'on' && opts.resources !== 'off') {
66
+ return { ok: false, message: '--resources 는 on|off 여야 합니다', exitCode: 1 };
67
+ }
64
68
 
65
69
  // ── mission 텍스트(verbatim: --mission-file 우선) ──
66
70
  const readFile = deps.readFile ?? ((p: string) => readFileSync(p, 'utf8'));
@@ -97,6 +101,8 @@ export async function runAgentMissionCliCommand(
97
101
  backend: agentBackend.name,
98
102
  ...(chain ? { chain: chain as ('codex' | 'claude' | 'elanous')[] } : {}),
99
103
  ...(plugin ? { plugin } : {}),
104
+ resources: opts.resources ?? 'on',
105
+ ...(opts.backend ? { backendExplicit: opts.backend } : {}),
100
106
  ...(opts.headless ? { headless: true } : {}),
101
107
  branch: opts.branch,
102
108
  ...(opts.base ? { base: opts.base } : {}),
@@ -15,6 +15,7 @@ import { ELANOUS_MARKET_URL } from '../plugins/install/market-fetch.js';
15
15
  const MARKETPLACE = 'elanous';
16
16
  const INDEX_URL = new URL('marketplace.json', ELANOUS_MARKET_URL).href; // one source of truth for the official market
17
17
  const MAX_NEEDS = 5;
18
+ const MAX_VOCABULARY = 200;
18
19
 
19
20
  export interface ResourcePlan {
20
21
  needs: string[];
@@ -29,11 +30,13 @@ export interface MissionResources {
29
30
  readonly backend?: string;
30
31
  readonly plugin?: string;
31
32
  readonly resources?: 'off' | 'on';
33
+ /** Total planner budget in milliseconds; discovery uses only the remaining time. */
34
+ readonly deadlineMs?: number;
32
35
  }
33
36
 
34
37
  export interface ResourceLadderDeps extends MissionResources {
35
38
  /** One JSON-producing LLM call; the raw mission is never sent to logs. */
36
- readonly inferNeeds?: (safeMission: string) => Promise<string>;
39
+ readonly inferNeeds?: (safeMission: string, vocabulary: readonly string[]) => Promise<string>;
37
40
  readonly readers?: CapabilityReaders;
38
41
  /** Read the official index and its detached signature as a pair; no other market is consulted. */
39
42
  readonly readOfficialIndex?: () => Promise<{ marketplaceBytes: Uint8Array; signatureText: string }>;
@@ -60,22 +63,25 @@ function safeNeed(value: unknown): string | null {
60
63
  return need;
61
64
  }
62
65
 
63
- function parseNeeds(raw: string): string[] {
66
+ function parseNeeds(raw: string, vocabulary: readonly string[]): string[] {
64
67
  let parsed: unknown;
65
68
  try { parsed = JSON.parse(raw); } catch { return []; }
66
69
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Array.isArray((parsed as { needs?: unknown }).needs)) return [];
67
70
  const needs: string[] = [];
68
71
  for (const item of (parsed as { needs: unknown[] }).needs) {
69
- const need = safeNeed(item);
70
- if (need && !needs.some(existing => normalizeServiceName(existing) === normalizeServiceName(need))) needs.push(need);
72
+ const label = typeof item === 'string' ? vocabulary.find(value => value === item) : undefined;
73
+ const need = label ?? safeNeed(item);
74
+ if (need && !needs.some(existing => normalizeServiceName(existing) === normalizeServiceName(need))) {
75
+ needs.push(label ?? vocabulary.find(value => normalizeServiceName(value) === normalizeServiceName(need)) ?? need);
76
+ }
71
77
  if (needs.length === MAX_NEEDS) break;
72
78
  }
73
79
  return needs;
74
80
  }
75
81
 
76
- async function inferNeeds(mission: string): Promise<string> {
82
+ async function inferNeeds(mission: string, vocabulary: readonly string[]): Promise<string> {
77
83
  return streamLLM([
78
- { role: 'system', content: 'Identify at most five concrete tool/service/skill capabilities required for this mission. Reply ONLY with JSON {"needs":["capability"]}. No credentials, tokens, URLs, or mission text in the output. If uncertain, return an empty list.' },
84
+ { role: 'system', content: `Identify at most five concrete tool/service/skill capabilities required for this mission. Reply ONLY with JSON {"needs":["capability"]}. No credentials, tokens, URLs, or mission text in the output. If a relevant label appears in this verified official marketplace vocabulary, use that exact label; otherwise use a concrete capability. If uncertain, return an empty list.\nVerified labels: ${JSON.stringify(vocabulary)}` },
79
85
  { role: 'user', content: mission },
80
86
  ], () => {}, { maxTokens: 200, temperature: 0 });
81
87
  }
@@ -104,20 +110,60 @@ function pluginLabels(plugin: MarketplaceIndex['plugins'][number]): string[] {
104
110
  return [plugin.name, plugin.description ?? '', ...plugin['ai.elanous'].capabilities, ...(plugin['ai.elanous'].vocab ?? [])];
105
111
  }
106
112
 
113
+ function officialVocabulary(index: MarketplaceIndex): string[] {
114
+ const labels: string[] = [];
115
+ for (const entry of index.plugins) {
116
+ for (const label of [entry.name, ...entry['ai.elanous'].capabilities, ...(entry['ai.elanous'].vocab ?? [])]) {
117
+ const safe = typeof label === 'string' && label === label.trim() && label.length <= 80
118
+ && !/[\n\r{}<>@=/\\]/.test(label) && (safeMissionText(label) === label || /^secret:[a-zA-Z0-9._-]+$/.test(label)) ? label : null;
119
+ if (safe && !labels.includes(safe)) labels.push(safe);
120
+ if (labels.length === MAX_VOCABULARY) return labels;
121
+ }
122
+ }
123
+ return labels;
124
+ }
125
+
126
+ function mentionedPluginNames(mission: string, index: MarketplaceIndex): string[] {
127
+ return index.plugins.map(entry => entry.name).filter(name => {
128
+ if (!safeNeed(name) || safeMissionText(name) !== name || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) return false;
129
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
130
+ return new RegExp(`(^|[^\\p{L}\\p{N}_.-])${escaped}(?=$|[^\\p{L}\\p{N}_.-])`, 'iu').test(mission);
131
+ }).slice(0, MAX_NEEDS);
132
+ }
133
+
107
134
  /** Pre-mission resource judgment. Only the optional, injected installer can cause installation. */
108
135
  export async function planMissionResources(mission: string, deps: ResourceLadderDeps = {}, options: MissionResources = {}): Promise<ResourcePlan> {
109
136
  const plan: ResourcePlan = { needs: [], have: [], gaps: [], decisions: [] };
110
137
  if (options.resources === 'off' || deps.resources === 'off') return plan;
111
138
  const backend = options.backend ?? deps.backend;
112
139
  const plugin = options.plugin ?? deps.plugin;
140
+ const budgetMs = options.deadlineMs ?? deps.deadlineMs;
141
+ const deadline = budgetMs === undefined ? undefined : Date.now() + budgetMs;
113
142
  const decide = (step: string, kind: DecisionEvent['kind'], what: string, why: string) => {
114
143
  plan.decisions.push(`${step}: ${what} — ${why}`);
115
144
  (deps.decide ?? emitDecision)({ kind, what, reason: why, purpose: 'pre-mission resource selection', target: 'agent-mission', phase: 'dispatch' });
116
145
  (deps.log ?? ((event, data) => debug.log('agent-mission.resources', event, data)))(step, { kind, what, why });
117
146
  };
118
147
 
119
- try { plan.needs = parseNeeds(await (deps.inferNeeds ?? inferNeeds)(safeMissionText(mission))); }
148
+ let index: MarketplaceIndex | undefined;
149
+ try {
150
+ const pair = await (deps.readOfficialIndex ?? readOfficialIndex)();
151
+ const checked = verifyIndex({ ...pair, trustedKeys: OFFICIAL_INDEX_KEYS });
152
+ if (checked.ok && checked.index.name === MARKETPLACE) index = checked.index;
153
+ else decide('official-index', 'ESCALATE', '공식 마켓 거부', '서명 또는 마켓 이름 검증 실패');
154
+ } catch { decide('official-index', 'ESCALATE', '공식 마켓 조회 실패', '서명 검증 가능한 원본 인덱스 없음'); }
155
+ const safeMission = safeMissionText(mission);
156
+ const vocabulary = index ? officialVocabulary(index) : [];
157
+ try { plan.needs = parseNeeds(await (deps.inferNeeds ?? inferNeeds)(safeMission, vocabulary), vocabulary); }
120
158
  catch { decide('needs', 'ESCALATE', '능력 판정 불가', 'LLM 응답 실패 — 추측하지 않음'); }
159
+ const mentioned = index ? mentionedPluginNames(safeMission, index) : [];
160
+ for (const name of mentioned) {
161
+ if (!plan.needs.some(need => matches(need, [name]))) {
162
+ if (plan.needs.length === MAX_NEEDS) plan.needs.pop();
163
+ plan.needs.push(name);
164
+ }
165
+ decide('needs', 'VERIFY', name, '이름 언급');
166
+ }
121
167
  decide('needs', 'VERIFY', '필요 능력 판정', `${plan.needs.length}개 확인`);
122
168
  if (!plan.needs.length) return plan;
123
169
 
@@ -132,16 +178,7 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
132
178
  });
133
179
  decide('have', 'VERIFY', 'ready 서비스 대조', `${plan.have.length}개 확인`);
134
180
 
135
- let index: MarketplaceIndex | undefined;
136
- if (remaining.length) {
137
- try {
138
- const pair = await (deps.readOfficialIndex ?? readOfficialIndex)();
139
- const checked = verifyIndex({ ...pair, trustedKeys: OFFICIAL_INDEX_KEYS });
140
- if (checked.ok && checked.index.name === MARKETPLACE) index = checked.index;
141
- else decide('official-index', 'ESCALATE', '공식 마켓 거부', '서명 또는 마켓 이름 검증 실패');
142
- } catch { decide('official-index', 'ESCALATE', '공식 마켓 조회 실패', '서명 검증 가능한 원본 인덱스 없음'); }
143
- }
144
- if (index) {
181
+ if (index && remaining.length) {
145
182
  let installed: readonly { name: string; market: string }[] = [];
146
183
  try { installed = (deps.readInstalledPlugins ?? installedPlugins)(); }
147
184
  catch { decide('have', 'ESCALATE', '설치 원장 읽기 실패', '설치된 스킬을 보유로 간주하지 않음'); }
@@ -153,7 +190,8 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
153
190
  });
154
191
  decide('have', 'VERIFY', '설치된 플러그인 스킬 대조', `${plan.have.length}개 확인`);
155
192
  if (!plugin) {
156
- const choices = index.plugins.filter(entry => remaining.some(need => matches(need, pluginLabels(entry))));
193
+ const named = index.plugins.filter(entry => mentioned.includes(entry.name) && remaining.some(need => matches(need, [entry.name])));
194
+ const choices = named.length ? named : index.plugins.filter(entry => remaining.some(need => matches(need, pluginLabels(entry))));
157
195
  if (choices.length === 1) {
158
196
  plan.plugin = { plugin: choices[0]!.name, marketplace: MARKETPLACE, source: 'official-index' };
159
197
  decide('official-index', 'ROUTE', '공식 플러그인 선택', '서명 검증된 인덱스에서 일치하는 플러그인 한 개');
@@ -169,13 +207,6 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
169
207
  } else decide('official-index', 'ROUTE', '명시 플러그인 보존', '호출자 선택을 덮지 않음');
170
208
  }
171
209
 
172
- for (const need of remaining) {
173
- let candidates: CapabilityCandidate[] = [];
174
- try { candidates = await (deps.discover ?? discoverCapability)(need, { community: false }); }
175
- catch { decide('discover', 'ESCALATE', '발굴 실패', '후보 없음 — 자동 설치하지 않음'); }
176
- plan.gaps.push({ need, candidates });
177
- decide('discover', 'ESCALATE', '미충족 능력 후보', `${candidates.length}개 후보 제안만 함`);
178
- }
179
210
  if (!backend) {
180
211
  const chosen = plan.needs.map(need => ({ need, name: pickBackend(matrix, need) })).find(row => row.name);
181
212
  if (chosen?.name) {
@@ -183,5 +214,22 @@ export async function planMissionResources(mission: string, deps: ResourceLadder
183
214
  decide('backend', 'ROUTE', 'ready 에이전트 선택', plan.backend.why);
184
215
  } else decide('backend', 'ESCALATE', '에이전트 자동 선택 보류', '필요 서비스의 ready 관측 없음');
185
216
  } else decide('backend', 'ROUTE', '명시 에이전트 보존', '호출자 backend 를 덮지 않음');
217
+ plan.gaps = await Promise.all(remaining.map(async need => {
218
+ let candidates: CapabilityCandidate[] = [];
219
+ let timer: ReturnType<typeof setTimeout> | undefined;
220
+ const controller = new AbortController();
221
+ try {
222
+ const remainingMs = deadline === undefined ? undefined : Math.max(0, deadline - Date.now() - 50);
223
+ if (remainingMs === 0) throw new Error('discovery deadline');
224
+ const discovery = (deps.discover ?? discoverCapability)(need, { community: false, signal: controller.signal });
225
+ candidates = remainingMs === undefined ? await discovery : await Promise.race([
226
+ discovery,
227
+ new Promise<CapabilityCandidate[]>((_resolve, reject) => { timer = setTimeout(() => reject(new Error('discovery deadline')), remainingMs); }),
228
+ ]);
229
+ } catch { controller.abort(); decide('discover', 'ESCALATE', '발굴 실패', '후보 없음 — 자동 설치하지 않음'); }
230
+ finally { if (timer) clearTimeout(timer); }
231
+ decide('discover', 'ESCALATE', '미충족 능력 후보', `${candidates.length}개 후보 제안만 함`);
232
+ return { need, candidates };
233
+ }));
186
234
  return plan;
187
235
  }