elanous 0.2.6 → 0.2.8

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 (517) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
  96. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/graphs/test-diet/recipes.yaml +9 -0
  178. package/graphs/test-diet/test-diet.yaml +22 -0
  179. package/package.json +6 -5
  180. package/persona-presets/_index.yaml +10 -0
  181. package/persona-presets/_schema.json +146 -0
  182. package/persona-presets/dana.yaml +26 -0
  183. package/persona-presets/haru.yaml +27 -0
  184. package/persona-presets/kai.yaml +27 -0
  185. package/persona-presets/leo.yaml +27 -0
  186. package/persona-presets/mira.yaml +30 -0
  187. package/persona-presets/nari.yaml +27 -0
  188. package/persona-presets/sora.yaml +27 -0
  189. package/persona-presets/yuna.yaml +27 -0
  190. package/plugins/card-followup/README.md +13 -4
  191. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  192. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  193. package/plugins/card-followup/graphs/run-step.ts +122 -18
  194. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  195. package/plugins/card-followup/plugin.json +1 -1
  196. package/plugins/doc-draft/README.md +13 -0
  197. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  198. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  199. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  200. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  201. package/plugins/doc-draft/plugin.json +24 -0
  202. package/plugins/geo-check/README.md +3 -1
  203. package/plugins/geo-check/graphs/run-step.ts +58 -13
  204. package/plugins/geo-check/plugin.json +1 -1
  205. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  206. package/plugins/job-coach/README.md +1 -1
  207. package/plugins/job-coach/graphs/run-step.ts +2 -2
  208. package/plugins/job-coach/plugin.json +1 -1
  209. package/plugins/job-coach/plugin.ts +1 -1
  210. package/scripts/affinity/endcard.sh +3 -2
  211. package/scripts/affinity/reframe.sh +3 -1
  212. package/scripts/ci-public-export-leak-gate.ts +48 -0
  213. package/scripts/codex-quota-alert.ts +122 -258
  214. package/scripts/coord-tracks.json +4 -4
  215. package/scripts/field-feed/deliver-node.ts +57 -0
  216. package/scripts/field-feed/feed-node.ts +67 -0
  217. package/scripts/field-feed/lib.ts +164 -0
  218. package/scripts/field-feed/reel-node.ts +24 -0
  219. package/scripts/field-feed/start.ts +62 -0
  220. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  221. package/scripts/install.sh +109 -21
  222. package/scripts/landing-heal/clean-checkout.ts +114 -0
  223. package/scripts/landing-heal/run-mission.ts +38 -3
  224. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  225. package/scripts/persona-presets/lint.ts +79 -0
  226. package/scripts/public-export.ts +8 -4
  227. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  228. package/scripts/release-loop/checklist-gate-node.ts +2 -2
  229. package/scripts/release-loop/env-known-failures.ts +31 -0
  230. package/scripts/release-loop/gate-node.ts +108 -22
  231. package/scripts/release-loop/known-issues-node.ts +159 -0
  232. package/scripts/release-loop/npm-publish-node.ts +124 -0
  233. package/scripts/release-loop/ops-upgrade-node.ts +95 -0
  234. package/scripts/release-loop/publish-node.ts +23 -1
  235. package/scripts/release-loop/release-readiness.ts +58 -0
  236. package/scripts/release-loop/shard-plan.ts +17 -0
  237. package/scripts/release-loop/unattended-release.ts +93 -0
  238. package/scripts/storyboard/storyboard.ts +167 -0
  239. package/scripts/test-diet/lib.ts +88 -0
  240. package/scripts/test-diet/node.ts +102 -0
  241. package/scripts/ux/measure-first-text.ts +102 -0
  242. package/scripts/vflow-collect.ts +20 -7
  243. package/scripts/vflow-search.ts +73 -0
  244. package/scripts/video-free-line-test-walker.ts +36 -0
  245. package/scripts/video-pipeline.ts +2 -2
  246. package/skills/explainer-video/SKILL.md +12 -9
  247. package/skills/explainer-video/engine/build.mjs +1 -1
  248. package/skills/explainer-video/engine/reel.mjs +138 -31
  249. package/skills/explainer-video/engine/reel.sh +2 -1
  250. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  251. package/skills/grill-me/SKILL.md +1 -1
  252. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  253. package/src/a2a/server.ts +2 -0
  254. package/src/acp/auth-methods.ts +30 -0
  255. package/src/acp/elanous-aware-peers.ts +30 -0
  256. package/src/acp/server.ts +96 -20
  257. package/src/agent/monad-agent-turn.ts +25 -6
  258. package/src/agent/shared-app-tools.ts +5 -3
  259. package/src/agent-substrate/review-intent.ts +6 -2
  260. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  261. package/src/auth/github-app-token.ts +4 -1
  262. package/src/autopilot/track-agent-shadow.ts +151 -0
  263. package/src/autopilot/track-agent.ts +23 -3
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/channel-import/detect.ts +191 -0
  271. package/src/channel-import/run.ts +111 -0
  272. package/src/chat/chat-cli.ts +4 -1
  273. package/src/chat/short-question.ts +40 -0
  274. package/src/cli/agent-cli.ts +24 -15
  275. package/src/cli/ask-bare.ts +42 -0
  276. package/src/cli/cli-user-error.ts +25 -0
  277. package/src/cli/codex-credit-plan-view.ts +25 -0
  278. package/src/cli/config-test-sync.ts +4 -0
  279. package/src/cli/decisions-cli.ts +13 -3
  280. package/src/cli/dependence-gauge.ts +237 -0
  281. package/src/cli/doctor-bundle.ts +128 -0
  282. package/src/cli/doctor-cli.ts +41 -1
  283. package/src/cli/doctor-local-llm.ts +95 -0
  284. package/src/cli/doctor-modes.ts +48 -0
  285. package/src/cli/import-cli.ts +60 -0
  286. package/src/cli/intake-cli.ts +12 -2
  287. package/src/cli/intake-source-cli.ts +58 -0
  288. package/src/cli/machine-cli.ts +23 -0
  289. package/src/cli/mcp-login.ts +13 -4
  290. package/src/cli/msg-cli.ts +137 -0
  291. package/src/cli/persona-cli.ts +116 -0
  292. package/src/cli/plugin-cli.ts +25 -0
  293. package/src/cli/pr-cli.ts +36 -0
  294. package/src/cli/provider-cli.ts +5 -0
  295. package/src/cli/release-cli.ts +50 -8
  296. package/src/cli/release-run-if-ready.ts +48 -0
  297. package/src/cli/setup-cli.ts +27 -6
  298. package/src/cli/setup-recommend.ts +8 -0
  299. package/src/cli/skills-cli.ts +173 -0
  300. package/src/cli/start-cli.ts +13 -4
  301. package/src/cli/usage-cli.ts +11 -1
  302. package/src/codex/setup.ts +37 -16
  303. package/src/dashboard/index.ts +18 -2
  304. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
  305. package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
  306. package/src/decisions/decision-cards.ts +291 -0
  307. package/src/decisions/decision-ledger.ts +5 -2
  308. package/src/decisions/discord-decision-cards.ts +133 -0
  309. package/src/decisions/telegram-decision-cards.ts +100 -0
  310. package/src/directives/version-at.ts +77 -0
  311. package/src/discord.ts +73 -6
  312. package/src/domains/core-tools.ts +37 -14
  313. package/src/domains/outbound-alert.ts +94 -12
  314. package/src/domains/repeated-failure.ts +1 -1
  315. package/src/error-report/report.ts +200 -0
  316. package/src/exec-requests/planner.ts +146 -0
  317. package/src/exec-requests/runner.ts +378 -0
  318. package/src/exec-requests/store.ts +78 -0
  319. package/src/field/field-feed-auto.ts +48 -0
  320. package/src/field/field-media.ts +8 -3
  321. package/src/field/field-reel.ts +203 -0
  322. package/src/grok/credential.ts +7 -5
  323. package/src/harness/aside-run-status.ts +33 -0
  324. package/src/harness/browser-verify.ts +48 -17
  325. package/src/harness/harness-cli-command.ts +25 -3
  326. package/src/harness/image-load-state.ts +21 -0
  327. package/src/harness/process-garbage.ts +45 -0
  328. package/src/hooks/error-report.ts +210 -0
  329. package/src/hooks/receiver.ts +41 -1
  330. package/src/import/apply.ts +128 -0
  331. package/src/import/plan.ts +197 -0
  332. package/src/index.ts +168 -21
  333. package/src/input-core/text-entry.ts +13 -5
  334. package/src/instance/leader.ts +13 -1
  335. package/src/intake-plane/collect-github.ts +5 -3
  336. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  337. package/src/intake-plane/discord-seat-work.ts +56 -0
  338. package/src/intake-plane/intake-sources.ts +202 -0
  339. package/src/intake-plane/items.ts +28 -12
  340. package/src/intake-plane/seat-answer.ts +96 -0
  341. package/src/intake-plane/seat-doc-route.ts +221 -0
  342. package/src/intake-plane/submit-intake-work.ts +9 -1
  343. package/src/intake-plane/telegram-seat-work.ts +72 -0
  344. package/src/llm/model-defaults.ts +24 -1
  345. package/src/llm.ts +5 -4
  346. package/src/machines/machine-ledger.ts +127 -0
  347. package/src/market/pack-internal-refs.ts +28 -0
  348. package/src/market/publish.ts +31 -4
  349. package/src/maturity/feature-maturity.ts +65 -0
  350. package/src/model-tier/turn-tier.ts +24 -0
  351. package/src/msg/msg-store.ts +149 -0
  352. package/src/native-tool-catalog.ts +2 -1
  353. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  354. package/src/nexus/api/consult-requests.ts +128 -0
  355. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  356. package/src/nexus/api/error-reports.ts +108 -0
  357. package/src/nexus/api/exec-requests.ts +51 -0
  358. package/src/nexus/api/field-uploads.ts +40 -3
  359. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  360. package/src/nexus/api/graph-approvals.ts +71 -4
  361. package/src/nexus/api/http-server.ts +82 -8
  362. package/src/nexus/api/outbound-report.ts +7 -0
  363. package/src/nexus/api/running-runs-background.ts +68 -0
  364. package/src/nexus/api/running-runs-child.ts +6 -0
  365. package/src/nexus/api/seat-requests.ts +254 -0
  366. package/src/nexus/api/share-captures.ts +91 -0
  367. package/src/nexus/api/skill-problems.ts +56 -0
  368. package/src/nexus/api/terminals.ts +24 -8
  369. package/src/nexus/api/trace-run-scan.ts +37 -0
  370. package/src/nexus/api/trace.ts +19 -3
  371. package/src/nexus/index.ts +28 -1
  372. package/src/nexus/setup-status.ts +11 -7
  373. package/src/notify/llm-budget-alert.ts +16 -10
  374. package/src/oauth/browser-availability.ts +14 -0
  375. package/src/oauth/codex-account-rotation.ts +26 -4
  376. package/src/oauth/codex-account-store.ts +20 -2
  377. package/src/oauth/codex-browser-login.ts +173 -0
  378. package/src/oauth/codex-quota-policy.ts +16 -0
  379. package/src/oauth/codex.ts +4 -1
  380. package/src/oauth/fallback-chain.ts +5 -5
  381. package/src/oauth/store.ts +42 -5
  382. package/src/onboarding/continue-here.ts +34 -0
  383. package/src/onboarding/entry-hints.ts +8 -0
  384. package/src/onboarding/full-screen-io.ts +35 -3
  385. package/src/onboarding/help/skills.md +1 -1
  386. package/src/onboarding/non-interactive.ts +1 -0
  387. package/src/onboarding/subscription-hint.ts +27 -0
  388. package/src/onboarding/web-first.ts +90 -0
  389. package/src/onboarding.ts +287 -26
  390. package/src/persona/presets.ts +105 -0
  391. package/src/plugins/maker/node-install.ts +92 -0
  392. package/src/plugins/maker/node-maker.ts +139 -0
  393. package/src/plugins/maker/plugin-maker.ts +19 -7
  394. package/src/policy/llm-policy.ts +268 -0
  395. package/src/prompt-library/session-guidance.ts +7 -1
  396. package/src/prompt-library/universal-preamble.ts +2 -2
  397. package/src/relay/relay-cli.ts +20 -0
  398. package/src/relay/relay-server.ts +220 -0
  399. package/src/release-loop/checklist.ts +37 -13
  400. package/src/self-dev/decompose.ts +1 -0
  401. package/src/self-dev/dev-pipeline.ts +6 -3
  402. package/src/self-dev/orchestrate.ts +18 -11
  403. package/src/self-dev/run-record-mirror.ts +44 -0
  404. package/src/self-dev/run-store.ts +12 -3
  405. package/src/self-implement/harness-policy-gates.ts +4 -1
  406. package/src/self-implement/report-origin.ts +12 -1
  407. package/src/session/chat.ts +17 -5
  408. package/src/shell-env-bootstrap.ts +44 -7
  409. package/src/skills/connect-claude-plugin.ts +142 -0
  410. package/src/skills/connect.ts +70 -0
  411. package/src/skills/index.ts +29 -1
  412. package/src/skills/repair.ts +124 -0
  413. package/src/skills/runner.ts +11 -2
  414. package/src/skills/shared-agent-skill-roots.ts +25 -0
  415. package/src/skills/trigger-extract.ts +1 -1
  416. package/src/steward/failure-streak.ts +78 -0
  417. package/src/steward/steward-cards.ts +24 -0
  418. package/src/steward/triage-budget.ts +127 -0
  419. package/src/steward/triage-plan.ts +75 -0
  420. package/src/steward/triage.ts +244 -42
  421. package/src/task-cards/card-store.ts +1 -1
  422. package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  424. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  426. package/src/telegram-commands.ts +13 -0
  427. package/src/telegram.ts +72 -2
  428. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  429. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  430. package/src/user-config.ts +189 -15
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/video-pipeline/recipes/character.ts +42 -5
  433. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  434. package/src/video-pipeline/recipes/free-line.ts +16 -3
  435. package/src/video-pipeline/recipes/remote.ts +7 -1
  436. package/src/video-pipeline/vflow-reference.ts +109 -0
  437. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  446. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  515. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  516. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  517. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
@@ -0,0 +1,139 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { join, resolve, sep } from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { debug } from '../../debug/log.js';
6
+ import { getNodeKindRegistration, listNodeKinds, unregisterPluginNodeKind } from '../../graph-kinds/registry.js';
7
+ import { loadPluginNodes } from '../../graph-kinds/plugin-nodes.js';
8
+ import { loadPluginManifestFromDir } from '../core/manifest.js';
9
+ import { codexWrite } from './plugin-maker.js';
10
+
11
+ export interface AddNodeOptions {
12
+ dir: string;
13
+ request: string;
14
+ kind?: string;
15
+ deps?: { codex: (dir: string, prompt: string) => Promise<void> };
16
+ }
17
+
18
+ export interface AddNodeResult {
19
+ status: 'added' | 'failed';
20
+ dir: string;
21
+ kind: string;
22
+ node: string;
23
+ errors: string[];
24
+ timings: { write: number; validate: number; repair?: number };
25
+ version?: string;
26
+ }
27
+
28
+ const KIND = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
29
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
30
+
31
+ function nodeErrors(dir: string, path: string, kind: string, manifest: ReturnType<typeof loadPluginManifestFromDir>['manifest']): string[] {
32
+ const errors: string[] = [];
33
+ let node: unknown;
34
+ try {
35
+ if (lstatSync(path).isSymbolicLink() || !realpathSync(path).startsWith(`${realpathSync(dir)}${sep}`)) {
36
+ return ['unsafe node path'];
37
+ }
38
+ node = parseYaml(readFileSync(path, 'utf8'));
39
+ } catch (error) { return [`node YAML read/parse failed: ${String(error)}`]; }
40
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return ['node must be an object'];
41
+ const spec = node as Record<string, unknown>;
42
+ if (spec.kind !== kind) errors.push(`node kind must be ${kind}`);
43
+ if (spec.graph !== 'workflow') errors.push('node graph must be workflow');
44
+ if (errors.length) return errors;
45
+ // The loader is the same validation path used by plugin installation. Register under a
46
+ // temporary identity so installed kinds cannot collide with this inspection.
47
+ const inspectionId = `inspect-${randomUUID().replaceAll('-', '')}`;
48
+ try {
49
+ errors.push(...loadPluginNodes(dir, { ...manifest, id: inspectionId,
50
+ contributes: { ...manifest.contributes, nodes: [`./nodes/${kind}.yaml`] } }).errors);
51
+ } finally {
52
+ for (const entry of listNodeKinds()) {
53
+ if (entry.plugin !== inspectionId) continue;
54
+ const registered = getNodeKindRegistration(entry.graph, entry.kind);
55
+ if (registered) unregisterPluginNodeKind(entry.graph, entry.kind, inspectionId, registered);
56
+ }
57
+ }
58
+ return errors;
59
+ }
60
+
61
+ export async function addNode({ dir: inputDir, request, kind: requestedKind, deps }: AddNodeOptions): Promise<AddNodeResult> {
62
+ const dir = resolve(inputDir);
63
+ const kind = requestedKind ?? request.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
64
+ if (!request.trim() || !KIND.test(kind)) throw new Error(`invalid node request or kind: ${kind}`);
65
+ const manifestFile = join(dir, 'plugin.json');
66
+ if (!existsSync(manifestFile) || lstatSync(manifestFile).isSymbolicLink()) throw new Error(`plugin.json missing or unsafe: ${manifestFile}`);
67
+ const { manifest } = loadPluginManifestFromDir(dir, { id: 'local-plugin' });
68
+ const original = readFileSync(manifestFile);
69
+ const raw: unknown = JSON.parse(original.toString('utf8'));
70
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw) || typeof (raw as Record<string, unknown>).version !== 'string'
71
+ || !VERSION.test((raw as Record<string, unknown>).version as string)) throw new Error('plugin.json requires a numeric major.minor.patch version');
72
+ const nodesDir = join(dir, 'nodes');
73
+ const nodesEntry = lstatSync(nodesDir, { throwIfNoEntry: false });
74
+ if (nodesEntry && !nodesEntry.isDirectory()) throw new Error(`unsafe nodes directory: ${nodesDir}`);
75
+ const path = join(nodesDir, `${kind}.yaml`);
76
+ if (lstatSync(path, { throwIfNoEntry: false }) || manifest.contributes.nodes?.some(node => node === `./nodes/${kind}.yaml`)) throw new Error(`node already exists: ${kind}`);
77
+ const root = realpathSync(dir);
78
+ for (const declared of manifest.contributes.nodes ?? []) {
79
+ const declaredPath = resolve(dir, declared);
80
+ const entry = lstatSync(declaredPath, { throwIfNoEntry: false });
81
+ if (!entry) continue;
82
+ if (!entry.isFile() || !realpathSync(declaredPath).startsWith(`${root}${sep}`)) {
83
+ throw new Error(`unsafe declared node path: ${declared}`);
84
+ }
85
+ const spec: unknown = parseYaml(readFileSync(declaredPath, 'utf8'));
86
+ if (spec && typeof spec === 'object' && !Array.isArray(spec)
87
+ && (spec as Record<string, unknown>).kind === kind) throw new Error(`node already exists: ${kind}`);
88
+ }
89
+ const timings: AddNodeResult['timings'] = { write: 0, validate: 0 };
90
+ const result: AddNodeResult = { status: 'failed', dir, kind, node: path, errors: [], timings };
91
+ const step = async (stage: keyof typeof timings, action: () => void | Promise<void>): Promise<void> => {
92
+ const start = performance.now();
93
+ try { await action(); }
94
+ finally { timings[stage] = Math.round(performance.now() - start); debug.log('plugin.node-maker', stage, { ms: timings[stage], errors: result.errors }); }
95
+ };
96
+ const prompt = `요청 원문:\n${request}\n\n이 플러그인에 workflow 노드 종류 ${kind} 하나만 작성하라. nodes/${kind}.yaml 파일만 작성하고 plugin.json 및 다른 파일은 변경하지 말 것. graph: workflow, kind: ${kind}, inputs: { type: object }, run: 의 bash/http/skill/mcp 중 정확히 하나를 정의하라. harness 노드 및 코드 실행기/다른 플러그인 파일은 만들지 말 것. 폴더 밖에는 쓰지 말 것.`;
97
+ try {
98
+ mkdirSync(nodesDir, { recursive: true });
99
+ await step('write', () => (deps?.codex ?? codexWrite)(dir, prompt));
100
+ const validate = () => {
101
+ const entry = lstatSync(manifestFile, { throwIfNoEntry: false });
102
+ if (!entry) { result.errors = ['plugin.json was deleted — restore it unchanged']; return; }
103
+ result.errors = !entry.isSymbolicLink() && readFileSync(manifestFile).equals(original)
104
+ ? nodeErrors(dir, path, kind, manifest) : ['plugin.json was modified'];
105
+ };
106
+ await step('validate', validate);
107
+ if (result.errors.length) {
108
+ await step('repair', () => (deps?.codex ?? codexWrite)(dir, `${prompt}\n\n아래 오류를 모두 수리하라:\n${result.errors.join('\n')}`));
109
+ await step('validate', validate);
110
+ }
111
+ if (result.errors.length) return result;
112
+ const data = raw as Record<string, unknown>;
113
+ const [, major, minor, patch] = (data.version as string).match(VERSION)!;
114
+ data.version = `${major}.${minor}.${BigInt(patch!) + 1n}`;
115
+ const extension = (data.extensions as Record<string, unknown> | undefined)?.['ai.elanous'];
116
+ if (extension && typeof extension === 'object' && !Array.isArray(extension)) {
117
+ const entry = extension as Record<string, unknown>;
118
+ entry.nodes = [...(manifest.contributes.nodes ?? []), `./nodes/${kind}.yaml`];
119
+ } else {
120
+ const contributes = (data.contributes ??= {}) as Record<string, unknown>;
121
+ contributes.nodes = [...(manifest.contributes.nodes ?? []), `./nodes/${kind}.yaml`];
122
+ }
123
+ writeFileSync(manifestFile, JSON.stringify(data, null, 2) + '\n');
124
+ result.version = data.version as string;
125
+ result.status = 'added';
126
+ } catch (error) {
127
+ result.errors.push(error instanceof Error ? error.message : String(error));
128
+ } finally {
129
+ if (result.status === 'failed') {
130
+ try {
131
+ if (lstatSync(manifestFile, { throwIfNoEntry: false })?.isSymbolicLink()) unlinkSync(manifestFile);
132
+ if (!existsSync(manifestFile) || !readFileSync(manifestFile).equals(original)) writeFileSync(manifestFile, original);
133
+ } catch (error) {
134
+ result.errors.push(`plugin.json restoration failed: ${String(error)}`);
135
+ }
136
+ }
137
+ }
138
+ return result;
139
+ }
@@ -1,4 +1,5 @@
1
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, readdirSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
2
3
  import { dirname, join, relative, resolve, sep } from 'node:path';
3
4
  import { parse as parseYaml } from 'yaml';
4
5
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
@@ -33,12 +34,23 @@ export interface MakePluginResult {
33
34
  timings: { scaffold: number; write: number; validate: number; repair?: number; install: number; run?: number };
34
35
  }
35
36
 
36
- export async function codexWrite(dir: string, prompt: string): Promise<void> {
37
- const child = Bun.spawn(['codex', 'exec', '-C', dir, '-s', 'workspace-write', '--skip-git-repo-check', '-o', join(dir, '.codex-last-message.txt'), prompt], {
38
- cwd: dir, stdin: 'ignore', stdout: 'ignore', stderr: 'pipe',
39
- });
40
- const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
41
- if (code !== 0) throw new Error(`codex exec failed (${code}): ${stderr.trim().slice(0, 500)}`);
37
+ /** Run codex in the plugin folder without leaving run debris there: the last message goes to a temp file, and a
38
+ * `.elanous/` that the run created (debug logs and a `latest` symlink from elanous processes codex starts in that
39
+ * folder) is removed — the installer refuses symlinks, so the debris made every freshly written plugin uninstallable
40
+ * (10-01 live `plugin node add`). A `.elanous/` that existed before the run is left alone. */
41
+ export async function codexWrite(dir: string, prompt: string, bin = 'codex'): Promise<void> {
42
+ const scratch = mkdtempSync(join(tmpdir(), 'elanous-codex-'));
43
+ const stateDirExisted = existsSync(join(dir, '.elanous'));
44
+ try {
45
+ const child = Bun.spawn([bin, 'exec', '-C', dir, '-s', 'workspace-write', '--skip-git-repo-check', '-o', join(scratch, 'last-message.txt'), prompt], {
46
+ cwd: dir, stdin: 'ignore', stdout: 'ignore', stderr: 'pipe',
47
+ });
48
+ const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
49
+ if (code !== 0) throw new Error(`codex exec failed (${code}): ${stderr.trim().slice(0, 500)}`);
50
+ } finally {
51
+ if (!stateDirExisted) rmSync(join(dir, '.elanous'), { recursive: true, force: true });
52
+ rmSync(scratch, { recursive: true, force: true });
53
+ }
42
54
  }
43
55
 
44
56
  function scaffold(dir: string, slug: string, request: string): void {
@@ -0,0 +1,268 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { isScalar, parseDocument } from 'yaml';
4
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
5
+ import { DEFAULT_FALLBACK_CHAIN, isFallbackStep, type FallbackStep } from '../oauth/fallback-chain.js';
6
+ import { isCodexQuotaPolicy } from '../oauth/codex-quota-policy.js';
7
+ import { DEFAULT_BUDGET_GATE_MAX_USED_PERCENT, MODEL_ROLES, RUNTIME_LLM_PROVIDER_NAMES, parseRoleLlmEntry, type RoleLlmConfig } from '../user-config.js';
8
+
9
+ export interface LlmPolicy {
10
+ version: 1;
11
+ default: { provider: string; model?: string };
12
+ roles: RoleLlmConfig;
13
+ fallback: FallbackStep[];
14
+ accounts: { codex: {
15
+ order: string[];
16
+ rotateAtPercent: number;
17
+ perAccount: Record<string, { rotateAtPercent: number; until?: string; why?: string }>;
18
+ rotationEnabled: boolean;
19
+ } };
20
+ credits: {
21
+ codex: 'use' | 'fallback' | 'never';
22
+ grants: Array<{ account: string; amount: number; expires: string; source: string }>;
23
+ pace?: { targetPerDay: number; until: string; why?: string };
24
+ };
25
+ caps: { codex: { harness: number; pod: number; tox: number; intake: number; headroom: number }; grok: { weekly: number; tox: number; intake: number } };
26
+ resetCredits: { redeem: 'human' };
27
+ alerts: { lowRemainingPercent: number; resetCreditExpiryDays: number; creditsStep: number };
28
+ }
29
+
30
+ export type LlmPolicySource = 'default' | 'legacy-config' | 'file';
31
+ export interface LlmPolicyValidation {
32
+ valid: boolean;
33
+ errors: string[];
34
+ warnings: string[];
35
+ }
36
+ export interface LoadedLlmPolicy extends LlmPolicyValidation {
37
+ policy: LlmPolicy;
38
+ sources: Record<string, LlmPolicySource>;
39
+ path: string;
40
+ filePresent: boolean;
41
+ }
42
+ export interface LoadLlmPolicyOptions {
43
+ /** All file IO remains scoped to this directory; tests can pass a temporary directory. */
44
+ configDir?: string;
45
+ path?: string;
46
+ /** An already-read legacy config; when omitted the sibling config.json is read directly. */
47
+ legacyConfig?: unknown;
48
+ now?: Date;
49
+ }
50
+
51
+ type Obj = Record<string, unknown>;
52
+ const object = (v: unknown): v is Obj => v !== null && typeof v === 'object' && !Array.isArray(v);
53
+ const percent = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 100;
54
+ const nonempty = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
55
+ const positive = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v > 0;
56
+ const date = (v: unknown): v is string => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) && !Number.isNaN(Date.parse(`${v}T00:00:00Z`)) && new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;
57
+
58
+ function setOwn(target: Obj, key: string, value: unknown): void {
59
+ Object.defineProperty(target, key, { value, configurable: true, enumerable: true, writable: true });
60
+ }
61
+
62
+ /** Today's code defaults, not a snapshot of one machine's user config. */
63
+ export function defaultLlmPolicy(): LlmPolicy {
64
+ return {
65
+ version: 1,
66
+ default: { provider: 'auto' },
67
+ roles: {},
68
+ fallback: [...DEFAULT_FALLBACK_CHAIN],
69
+ accounts: { codex: { order: [], rotateAtPercent: 95, perAccount: { default: { rotateAtPercent: 60 } }, rotationEnabled: true } },
70
+ credits: { codex: 'fallback', grants: [] },
71
+ caps: {
72
+ codex: { harness: DEFAULT_BUDGET_GATE_MAX_USED_PERCENT['openai-codex']!, pod: 95, tox: 60, intake: 80, headroom: 15 },
73
+ grok: { weekly: DEFAULT_BUDGET_GATE_MAX_USED_PERCENT.grok!, tox: 50, intake: 48 },
74
+ },
75
+ resetCredits: { redeem: 'human' },
76
+ alerts: { lowRemainingPercent: 10, resetCreditExpiryDays: 7, creditsStep: 5000 },
77
+ };
78
+ }
79
+
80
+ function leafPaths(value: unknown, prefix = ''): string[] {
81
+ if (!object(value) || Object.keys(value).length === 0) return prefix ? [prefix] : [];
82
+ return Object.entries(value).flatMap(([key, item]) => leafPaths(item, prefix ? `${prefix}.${key}` : key));
83
+ }
84
+
85
+ /** Validate the partial policy document. Unknown fields and invalid values are errors, not silent overrides. */
86
+ export function validateLlmPolicy(input: unknown): LlmPolicyValidation {
87
+ const errors: string[] = [];
88
+ const warnings: string[] = [];
89
+ if (!object(input)) return { valid: false, errors: ['policy must be a mapping'], warnings };
90
+ const check = (node: unknown, path: string, schema: Obj): void => {
91
+ if (!object(node)) { errors.push(`${path} must be a mapping`); return; }
92
+ for (const [key, value] of Object.entries(node)) {
93
+ if (!Object.hasOwn(schema, key)) { errors.push(`unknown field ${path}.${key}`); continue; }
94
+ const rule = schema[key];
95
+ if (object(rule)) check(value, `${path}.${key}`, rule);
96
+ else if (typeof rule === 'function' && !(rule as (v: unknown) => boolean)(value)) errors.push(`${path}.${key} is invalid`);
97
+ }
98
+ };
99
+ const dateField = (v: unknown) => date(v);
100
+ const perAccount = (v: unknown) => object(v) && Object.entries(v).every(([key, row]) => nonempty(key) && object(row)
101
+ && Object.keys(row).every(k => ['rotateAtPercent', 'until', 'why'].includes(k))
102
+ && percent(row.rotateAtPercent) && (row.until === undefined || date(row.until)) && (row.why === undefined || nonempty(row.why)));
103
+ const grants = (v: unknown) => Array.isArray(v) && v.every(g => object(g)
104
+ && Object.keys(g).every(k => ['account', 'amount', 'expires', 'source'].includes(k))
105
+ && nonempty(g.account) && positive(g.amount) && date(g.expires) && nonempty(g.source));
106
+ const roles = (v: unknown) => object(v) && Object.entries(v).every(([role, spec]) => MODEL_ROLES.some(r => r === role)
107
+ && object(spec) && Object.keys(spec).every(k => ['provider', 'tier', 'model'].includes(k)) && parseRoleLlmEntry(spec).ok);
108
+ check(input, 'policy', {
109
+ version: (v: unknown) => v === 1,
110
+ default: { provider: (v: unknown) => nonempty(v) && RUNTIME_LLM_PROVIDER_NAMES.some(p => p === v), model: nonempty },
111
+ roles,
112
+ fallback: (v: unknown) => Array.isArray(v) && v.length > 0 && v.every(isFallbackStep) && new Set(v).size === v.length,
113
+ accounts: { codex: {
114
+ order: (v: unknown) => Array.isArray(v) && v.every(nonempty) && new Set(v).size === v.length,
115
+ rotateAtPercent: percent, perAccount, rotationEnabled: (v: unknown) => typeof v === 'boolean',
116
+ } },
117
+ credits: {
118
+ codex: (v: unknown) => v === 'use' || v === 'fallback' || v === 'never',
119
+ grants,
120
+ pace: (v: unknown) => object(v) && positive(v.targetPerDay) && dateField(v.until)
121
+ && (v.why === undefined || nonempty(v.why))
122
+ && Object.keys(v).every(k => ['targetPerDay', 'until', 'why'].includes(k)),
123
+ },
124
+ caps: { codex: { harness: percent, pod: percent, tox: percent, intake: percent, headroom: percent },
125
+ grok: { weekly: percent, tox: percent, intake: percent } },
126
+ resetCredits: { redeem: (v: unknown) => v === 'human' },
127
+ alerts: { lowRemainingPercent: percent, resetCreditExpiryDays: positive, creditsStep: positive },
128
+ });
129
+ if (input.version !== undefined && input.version !== 1) warnings.push('unsupported policy version');
130
+ return { valid: errors.length === 0, errors, warnings };
131
+ }
132
+
133
+ function merge(base: Obj, overlay: Obj, sources: Record<string, LlmPolicySource>, source: LlmPolicySource, prefix = ''): void {
134
+ for (const [key, value] of Object.entries(overlay)) {
135
+ const path = prefix ? `${prefix}.${key}` : key;
136
+ if (object(value) && Object.hasOwn(base, key) && object(base[key])) merge(base[key] as Obj, value, sources, source, path);
137
+ else {
138
+ setOwn(base, key, value !== null && typeof value === 'object' ? structuredClone(value) : value);
139
+ for (const leaf of leafPaths(value, path)) setOwn(sources, leaf, source);
140
+ }
141
+ }
142
+ }
143
+
144
+ /** The date is inclusive: a rule with until=2026-10-29 remains active through that UTC day. */
145
+ function removeExpired(input: Obj, now: Date, warnings: string[], prefix = ''): void {
146
+ for (const [key, value] of Object.entries(input)) {
147
+ const path = prefix ? `${prefix}.${key}` : key;
148
+ if (path === 'credits.grants' && Array.isArray(value)) {
149
+ input[key] = value.filter(grant => {
150
+ if ((grant as { expires: string }).expires >= now.toISOString().slice(0, 10)) return true;
151
+ warnings.push(`${path} ${String((grant as { account: string }).account)} expired on ${String((grant as { expires: string }).expires)}; ignored`);
152
+ return false;
153
+ });
154
+ continue;
155
+ }
156
+ if (!object(value)) continue;
157
+ if (typeof value.until === 'string' && date(value.until) && value.until < now.toISOString().slice(0, 10)) {
158
+ delete input[key];
159
+ warnings.push(`${path} expired on ${value.until}; ignored`);
160
+ } else removeExpired(value, now, warnings, path);
161
+ }
162
+ }
163
+
164
+ export function loadLlmPolicy(opts: LoadLlmPolicyOptions = {}): LoadedLlmPolicy {
165
+ const configDir = opts.configDir ?? getElanousConfigDir();
166
+ const path = opts.path ?? join(configDir, 'policy', 'llm.yaml');
167
+ const policy = defaultLlmPolicy();
168
+ const sources: Record<string, LlmPolicySource> = Object.fromEntries(leafPaths(policy).map(p => [p, 'default']));
169
+ const warnings: string[] = [];
170
+ const errors: string[] = [];
171
+ let legacy: unknown = opts.legacyConfig;
172
+ if (legacy === undefined) {
173
+ const legacyPath = join(configDir, 'config.json');
174
+ try { legacy = JSON.parse(readFileSync(legacyPath, 'utf8')); }
175
+ catch (error) {
176
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
177
+ warnings.push(`legacy config ${legacyPath} could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
178
+ }
179
+ legacy = {};
180
+ }
181
+ }
182
+ if (object(legacy)) {
183
+ const llm = object(legacy.llm) ? legacy.llm : {};
184
+ const overlay: Obj = {};
185
+ const add = (key: string, value: unknown, target: Obj, targetKey: string, valid: (v: unknown) => boolean): void => {
186
+ if (value === undefined) return;
187
+ if (valid(value)) { setOwn(target, targetKey, value); warnings.push(`legacy config ${key} applies; migrate to policy/llm.yaml`); }
188
+ else warnings.push(`legacy config ${key} is invalid; ignored`);
189
+ };
190
+ const base: Obj = {};
191
+ add('llm.provider', llm.provider, base, 'provider', v => nonempty(v) && RUNTIME_LLM_PROVIDER_NAMES.some(p => p === v));
192
+ add('llm.model', llm.model, base, 'model', nonempty);
193
+ if (Object.keys(base).length) overlay.default = base;
194
+ add('roleLlm', legacy.roleLlm, overlay, 'roles', v => object(v) && Object.entries(v).every(([r, spec]) => MODEL_ROLES.some(k => k === r) && parseRoleLlmEntry(spec).ok));
195
+ add('llm.fallbackChain', llm.fallbackChain, overlay, 'fallback', v => Array.isArray(v) && v.length > 0 && v.every(isFallbackStep) && new Set(v).size === v.length);
196
+ const codex: Obj = {};
197
+ add('llm.codexAccountOrder', llm.codexAccountOrder, codex, 'order', v => Array.isArray(v) && v.every(nonempty));
198
+ add('llm.codexAccountRotation', llm.codexAccountRotation, codex, 'rotationEnabled', v => typeof v === 'boolean');
199
+ add('llm.codexAccountRotationThresholdPercent', llm.codexAccountRotationThresholdPercent, codex, 'rotateAtPercent', percent);
200
+ if (object(llm.codexAccountRotationThresholdPercentByAccount)) {
201
+ const rows: Obj = {};
202
+ for (const [account, value] of Object.entries(llm.codexAccountRotationThresholdPercentByAccount)) {
203
+ add(`llm.codexAccountRotationThresholdPercentByAccount.${account}`, value, rows, account, percent);
204
+ if (Object.hasOwn(rows, account)) setOwn(rows, account, { rotateAtPercent: rows[account] });
205
+ }
206
+ if (Object.keys(rows).length) codex.perAccount = rows;
207
+ }
208
+ if (Object.keys(codex).length) overlay.accounts = { codex };
209
+ const credits: Obj = {};
210
+ if (llm.codexQuotaPolicy !== undefined) {
211
+ if (isCodexQuotaPolicy(llm.codexQuotaPolicy)) {
212
+ credits.codex = { credits: 'use', fallback: 'fallback', 'within-quota': 'never' }[llm.codexQuotaPolicy];
213
+ warnings.push('legacy config llm.codexQuotaPolicy applies; migrate to policy/llm.yaml');
214
+ } else warnings.push('legacy config llm.codexQuotaPolicy is invalid; ignored');
215
+ }
216
+ if (credits.codex === undefined && llm.codexCreditsAllowed === true) {
217
+ credits.codex = 'use';
218
+ warnings.push('legacy config llm.codexCreditsAllowed applies; migrate to policy/llm.yaml');
219
+ }
220
+ if (Object.keys(credits).length) overlay.credits = credits;
221
+ const gate = object(legacy.harness) && object(legacy.harness.budgetGate) ? legacy.harness.budgetGate : {};
222
+ const caps: Obj = {};
223
+ const harness: Obj = {};
224
+ add('harness.budgetGate.minHeadroomPercent', gate.minHeadroomPercent, harness, 'headroom', percent);
225
+ if (object(gate.maxUsedPercent)) {
226
+ add('harness.budgetGate.maxUsedPercent.openai-codex', gate.maxUsedPercent['openai-codex'], harness, 'harness', percent);
227
+ const grok: Obj = {};
228
+ add('harness.budgetGate.maxUsedPercent.grok', gate.maxUsedPercent.grok, grok, 'weekly', percent);
229
+ if (Object.keys(grok).length) caps.grok = grok;
230
+ }
231
+ if (Object.keys(harness).length) caps.codex = harness;
232
+ if (Object.keys(caps).length) overlay.caps = caps;
233
+ merge(policy as unknown as Obj, overlay, sources, 'legacy-config');
234
+ }
235
+ // Read directly: `existsSync` is also false when the folder cannot be searched (EACCES), which would
236
+ // report an unreadable policy as «no file · valid». Only ENOENT/ENOTDIR mean the file is absent.
237
+ let raw: string | undefined;
238
+ let filePresent = false;
239
+ try {
240
+ raw = readFileSync(path, 'utf8');
241
+ filePresent = true;
242
+ } catch (error) {
243
+ const code = (error as NodeJS.ErrnoException).code;
244
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
245
+ filePresent = true;
246
+ errors.push(`cannot read policy file ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`);
247
+ }
248
+ }
249
+ if (raw !== undefined) {
250
+ try {
251
+ const doc = parseDocument(raw, { uniqueKeys: true });
252
+ if (doc.errors.length) throw new Error(doc.errors.map(e => e.message).join('; '));
253
+ const emptyDocument = doc.contents === null || (isScalar(doc.contents) && doc.contents.type === 'PLAIN' && doc.contents.source === '');
254
+ const parsed: unknown = emptyDocument ? {} : doc.toJS();
255
+ const validation = validateLlmPolicy(parsed);
256
+ errors.push(...validation.errors);
257
+ warnings.push(...validation.warnings);
258
+ if (validation.valid && object(parsed)) {
259
+ const filtered = structuredClone(parsed);
260
+ removeExpired(filtered, opts.now ?? new Date(), warnings);
261
+ merge(policy as unknown as Obj, filtered, sources, 'file');
262
+ }
263
+ } catch (error) {
264
+ errors.push(`invalid policy file ${path}: ${error instanceof Error ? error.message : String(error)}`);
265
+ }
266
+ }
267
+ return { policy, sources, path, filePresent, valid: errors.length === 0, errors, warnings };
268
+ }
@@ -115,9 +115,15 @@ export function buildSessionGuidanceAddendum(
115
115
  }
116
116
 
117
117
  if (items.length === 0) return [];
118
+ // B4 — 파일 수 규칙. 명령을 실제로 돌릴 도구(Bash·PtyShellStart·WebTerminal*)가 있을 때만 `git ls-files` 로 세라고 하고,
119
+ // Read 만 켜진 세션에선 셀 수 없으니 추측하지 말고 확인 불가라고 답하게 한다(EVAL q09 «959» vs 실측 778 · 리뷰 3라운드 must-fix).
120
+ const canRunCommands = enabledTools.some(name => /^(Bash|PtyShellStart|WebTerminal)/.test(name));
121
+ const fileCountRule = canRunCommands
122
+ ? '저장소의 파일 수·목록은 저장소 루트에서 `git ls-files <경로>`로 추적 파일 기준으로 센다 — 하위 디렉터리에서 실행 중이면 저장소 루트로 이동해서 세고, 다른 기준이면 그 기준을 답에 적는다.'
123
+ : '이 세션에는 명령을 실행할 도구가 없다 — 저장소의 파일 수는 추측하지 말고 «이 모드에서는 셀 수 없다»고 답한다(도구로 센 목록이 대화에 있으면 그 기준을 밝히고 쓴다).';
118
124
  return [{
119
125
  role: 'system',
120
- content: '# Session-specific guidance\n\n' +
126
+ content: `${fileCountRule}\n\n` + '# Session-specific guidance\n\n' +
121
127
  items.map(s => `- ${s}`).join('\n'),
122
128
  }];
123
129
  }
@@ -60,8 +60,8 @@ export const PROJECT_TREE_MAX_CHARS = 8 * 1024;
60
60
  export const PROJECT_TREE_DIR_ENTRY_LIMIT = 20;
61
61
 
62
62
  const NO_TOOLS_VERIFICATION_GUIDANCE = `## Chat without tools — verify rather than guess
63
- This conversation has tools disabled. Do not guess repository or real-time facts that require verification, such as file counts, file contents, commits, or the current time. Say you cannot check them here. The project tree above is for navigation, not evidence for counts.
64
- To check these facts, enable tools with \`elanous agent …\` or \`elanous chat --tools …\`.`;
63
+ This conversation has tools disabled. Do not guess repository or real-time facts that require verification, such as file counts, file contents, commits, or the current date or time. When asked for facts you cannot verify here (including repository state, files, and dates), explicitly say you cannot check them in this mode. The project tree above is for navigation, not evidence for counts.
64
+ Give one line explaining how to enable tools: \`elanous agent "<your question>"\`.`;
65
65
 
66
66
  // P4 (2026-05-03) — Family-agnostic coding-agent lifecycle. Maps the
67
67
  // three user-facing pipelines to an explicit tool-batch sequence so every
@@ -0,0 +1,20 @@
1
+ import type { Command } from 'commander';
2
+
3
+ /** `elanous relay serve` — 릴레이 서버(R2 · 내부 문서 `RFC-mobile-relay-without-tailscale-2026-09-30` A5).
4
+ * index.ts 원문을 잘라 보는 시험(첫 `.command('serve')` = mcp)을 흔들지 않게 등록을 여기 둔다. */
5
+ export function registerRelayCommand(program: Command): void {
6
+ const relayCmd = program.command('relay').description('Encrypted WebSocket relay');
7
+ relayCmd.command('serve')
8
+ .description('Serve paired relay connections using operator-provisioned host signing keys')
9
+ .requiredOption('--trusted-host-keys <file>', 'JSON object of X25519-derived serverId to Ed25519 public key (base64url)')
10
+ .option('--port <port>', 'Listening port', '8765')
11
+ .action(async (opts: { trustedHostKeys: string; port: string }) => {
12
+ const port = Number(opts.port);
13
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('invalid relay port');
14
+ const { loadTrustedHostKeys, startRelayServer } = await import('./relay-server.js');
15
+ const relay = startRelayServer({ port, trustedHostKeys: loadTrustedHostKeys(opts.trustedHostKeys) });
16
+ console.log(`relay listening on 127.0.0.1:${relay.port}`);
17
+ process.once('SIGINT', () => { relay.stop(); });
18
+ process.once('SIGTERM', () => { relay.stop(); });
19
+ });
20
+ }