elanous 0.2.6 → 0.2.7

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 (450) 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-2eba6079f31b88d4.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/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.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-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.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-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.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-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.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/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_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/package.json +5 -5
  178. package/plugins/card-followup/README.md +13 -4
  179. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  180. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  181. package/plugins/card-followup/graphs/run-step.ts +122 -18
  182. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  183. package/plugins/card-followup/plugin.json +1 -1
  184. package/plugins/doc-draft/README.md +13 -0
  185. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  186. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  187. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  188. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  189. package/plugins/doc-draft/plugin.json +24 -0
  190. package/plugins/geo-check/README.md +3 -1
  191. package/plugins/geo-check/graphs/run-step.ts +24 -3
  192. package/plugins/geo-check/plugin.json +1 -1
  193. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  194. package/plugins/job-coach/README.md +1 -1
  195. package/plugins/job-coach/graphs/run-step.ts +2 -2
  196. package/plugins/job-coach/plugin.json +1 -1
  197. package/plugins/job-coach/plugin.ts +1 -1
  198. package/scripts/affinity/endcard.sh +3 -2
  199. package/scripts/affinity/reframe.sh +3 -1
  200. package/scripts/ci-public-export-leak-gate.ts +48 -0
  201. package/scripts/codex-quota-alert.ts +122 -258
  202. package/scripts/coord-tracks.json +4 -4
  203. package/scripts/field-feed/deliver-node.ts +52 -0
  204. package/scripts/field-feed/feed-node.ts +67 -0
  205. package/scripts/field-feed/lib.ts +162 -0
  206. package/scripts/field-feed/reel-node.ts +24 -0
  207. package/scripts/field-feed/start.ts +62 -0
  208. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  209. package/scripts/install.sh +92 -13
  210. package/scripts/landing-heal/clean-checkout.ts +114 -0
  211. package/scripts/landing-heal/run-mission.ts +38 -3
  212. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  213. package/scripts/public-export.ts +8 -4
  214. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  215. package/scripts/release-loop/env-known-failures.ts +31 -0
  216. package/scripts/release-loop/gate-node.ts +81 -19
  217. package/scripts/release-loop/known-issues-node.ts +159 -0
  218. package/scripts/release-loop/npm-publish-node.ts +118 -0
  219. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  220. package/scripts/release-loop/release-readiness.ts +58 -0
  221. package/scripts/release-loop/unattended-release.ts +93 -0
  222. package/scripts/ux/measure-first-text.ts +102 -0
  223. package/scripts/vflow-collect.ts +20 -7
  224. package/scripts/vflow-search.ts +73 -0
  225. package/scripts/video-free-line-test-walker.ts +36 -0
  226. package/scripts/video-pipeline.ts +2 -2
  227. package/skills/explainer-video/SKILL.md +12 -9
  228. package/skills/explainer-video/engine/build.mjs +1 -1
  229. package/skills/explainer-video/engine/reel.mjs +138 -31
  230. package/skills/explainer-video/engine/reel.sh +2 -1
  231. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  232. package/skills/grill-me/SKILL.md +1 -1
  233. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  234. package/src/a2a/server.ts +2 -0
  235. package/src/acp/elanous-aware-peers.ts +30 -0
  236. package/src/acp/server.ts +91 -15
  237. package/src/agent/monad-agent-turn.ts +25 -6
  238. package/src/agent/shared-app-tools.ts +5 -3
  239. package/src/agent-substrate/review-intent.ts +6 -2
  240. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  241. package/src/auth/github-app-token.ts +4 -1
  242. package/src/autopilot/track-agent-shadow.ts +151 -0
  243. package/src/autopilot/track-agent.ts +23 -3
  244. package/src/boot/acp-server-help.ts +16 -0
  245. package/src/boot/daemon-prompt-turn.ts +31 -10
  246. package/src/boot/daemon-tools/index.ts +7 -1
  247. package/src/budget/codex-credit-pace.ts +84 -0
  248. package/src/budget/codex-credit-plan.ts +154 -0
  249. package/src/budget/codex-reset-credit-state.ts +2 -0
  250. package/src/chat/chat-cli.ts +4 -1
  251. package/src/chat/short-question.ts +40 -0
  252. package/src/cli/agent-cli.ts +24 -15
  253. package/src/cli/ask-bare.ts +42 -0
  254. package/src/cli/cli-user-error.ts +25 -0
  255. package/src/cli/codex-credit-plan-view.ts +25 -0
  256. package/src/cli/dependence-gauge.ts +237 -0
  257. package/src/cli/doctor-cli.ts +5 -0
  258. package/src/cli/doctor-modes.ts +48 -0
  259. package/src/cli/intake-cli.ts +12 -2
  260. package/src/cli/intake-source-cli.ts +58 -0
  261. package/src/cli/machine-cli.ts +23 -0
  262. package/src/cli/msg-cli.ts +137 -0
  263. package/src/cli/plugin-cli.ts +25 -0
  264. package/src/cli/pr-cli.ts +36 -0
  265. package/src/cli/provider-cli.ts +5 -0
  266. package/src/cli/release-cli.ts +42 -1
  267. package/src/cli/release-run-if-ready.ts +48 -0
  268. package/src/cli/setup-cli.ts +27 -6
  269. package/src/cli/setup-recommend.ts +8 -0
  270. package/src/cli/usage-cli.ts +11 -1
  271. package/src/codex/setup.ts +27 -8
  272. package/src/dashboard/index.ts +1 -1
  273. package/src/discord.ts +56 -6
  274. package/src/domains/core-tools.ts +37 -14
  275. package/src/domains/outbound-alert.ts +50 -11
  276. package/src/exec-requests/planner.ts +140 -0
  277. package/src/exec-requests/runner.ts +338 -0
  278. package/src/exec-requests/store.ts +76 -0
  279. package/src/field/field-media.ts +8 -3
  280. package/src/field/field-reel.ts +196 -0
  281. package/src/grok/credential.ts +7 -5
  282. package/src/harness/aside-run-status.ts +33 -0
  283. package/src/harness/browser-verify.ts +48 -17
  284. package/src/harness/harness-cli-command.ts +25 -3
  285. package/src/harness/image-load-state.ts +21 -0
  286. package/src/harness/process-garbage.ts +45 -0
  287. package/src/index.ts +86 -14
  288. package/src/instance/leader.ts +13 -1
  289. package/src/intake-plane/collect-github.ts +5 -3
  290. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  291. package/src/intake-plane/discord-seat-work.ts +56 -0
  292. package/src/intake-plane/intake-sources.ts +202 -0
  293. package/src/intake-plane/items.ts +28 -12
  294. package/src/intake-plane/seat-doc-route.ts +207 -0
  295. package/src/intake-plane/submit-intake-work.ts +9 -1
  296. package/src/intake-plane/telegram-seat-work.ts +72 -0
  297. package/src/llm/model-defaults.ts +24 -1
  298. package/src/llm.ts +5 -4
  299. package/src/machines/machine-ledger.ts +127 -0
  300. package/src/market/pack-internal-refs.ts +28 -0
  301. package/src/market/publish.ts +31 -4
  302. package/src/maturity/feature-maturity.ts +65 -0
  303. package/src/model-tier/turn-tier.ts +24 -0
  304. package/src/msg/msg-store.ts +149 -0
  305. package/src/native-tool-catalog.ts +2 -1
  306. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  307. package/src/nexus/api/consult-requests.ts +117 -0
  308. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  309. package/src/nexus/api/exec-requests.ts +51 -0
  310. package/src/nexus/api/field-uploads.ts +40 -3
  311. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  312. package/src/nexus/api/graph-approvals.ts +71 -4
  313. package/src/nexus/api/http-server.ts +61 -7
  314. package/src/nexus/api/seat-requests.ts +254 -0
  315. package/src/nexus/api/trace-run-scan.ts +37 -0
  316. package/src/nexus/api/trace.ts +19 -3
  317. package/src/nexus/index.ts +3 -0
  318. package/src/nexus/setup-status.ts +4 -5
  319. package/src/notify/llm-budget-alert.ts +16 -10
  320. package/src/oauth/codex-account-rotation.ts +26 -4
  321. package/src/oauth/codex-account-store.ts +20 -2
  322. package/src/oauth/codex-browser-login.ts +173 -0
  323. package/src/oauth/codex.ts +4 -1
  324. package/src/oauth/fallback-chain.ts +5 -5
  325. package/src/oauth/store.ts +42 -5
  326. package/src/onboarding/continue-here.ts +29 -0
  327. package/src/onboarding/entry-hints.ts +8 -0
  328. package/src/onboarding/full-screen-io.ts +35 -3
  329. package/src/onboarding/help/skills.md +1 -1
  330. package/src/onboarding/non-interactive.ts +1 -0
  331. package/src/onboarding/subscription-hint.ts +27 -0
  332. package/src/onboarding/web-first.ts +83 -0
  333. package/src/onboarding.ts +230 -23
  334. package/src/plugins/maker/node-install.ts +92 -0
  335. package/src/plugins/maker/node-maker.ts +139 -0
  336. package/src/plugins/maker/plugin-maker.ts +19 -7
  337. package/src/policy/llm-policy.ts +268 -0
  338. package/src/prompt-library/session-guidance.ts +7 -1
  339. package/src/prompt-library/universal-preamble.ts +2 -2
  340. package/src/relay/relay-cli.ts +20 -0
  341. package/src/relay/relay-server.ts +220 -0
  342. package/src/release-loop/checklist.ts +9 -8
  343. package/src/self-dev/decompose.ts +1 -0
  344. package/src/self-dev/dev-pipeline.ts +6 -3
  345. package/src/self-dev/run-record-mirror.ts +44 -0
  346. package/src/self-dev/run-store.ts +12 -3
  347. package/src/self-implement/harness-policy-gates.ts +4 -1
  348. package/src/self-implement/report-origin.ts +12 -1
  349. package/src/session/chat.ts +17 -5
  350. package/src/skills/shared-agent-skill-roots.ts +25 -0
  351. package/src/steward/failure-streak.ts +78 -0
  352. package/src/steward/steward-cards.ts +24 -0
  353. package/src/steward/triage-budget.ts +127 -0
  354. package/src/steward/triage-plan.ts +75 -0
  355. package/src/steward/triage.ts +244 -42
  356. package/src/task-cards/card-store.ts +1 -1
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  358. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  359. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  360. package/src/telegram.ts +38 -2
  361. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  362. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  363. package/src/user-config.ts +80 -4
  364. package/src/version/packed-revision.json +1 -1
  365. package/src/video-pipeline/recipes/character.ts +42 -5
  366. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  367. package/src/video-pipeline/recipes/free-line.ts +16 -3
  368. package/src/video-pipeline/recipes/remote.ts +7 -1
  369. package/src/video-pipeline/vflow-reference.ts +109 -0
  370. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  379. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  448. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  449. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  450. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -12,6 +12,7 @@ import { OFFICIAL_INDEX_KEYS } from '../market/official-keys.js';
12
12
  import { installPlugin, listInstalledPlugins, PluginInstallError, removePlugin, type InstallEvent } from '../plugins/install/plugin-install.js';
13
13
  import { credentialStatus, setPluginCredentials } from '../plugins/install/plugin-credentials.js';
14
14
  import { makePlugin } from '../plugins/maker/plugin-maker.js';
15
+ import { addAndInstallNode } from '../plugins/maker/node-install.js';
15
16
 
16
17
  function keys(): ReadonlyArray<{ keyId: string; publicKey: string }> {
17
18
  const configPath = userConfigPath();
@@ -83,6 +84,30 @@ export function registerPluginCommands(program: Command): void {
83
84
  process.exitCode = 1;
84
85
  }
85
86
  });
87
+ plugin.command('node').description('Manage plugin workflow nodes')
88
+ .command('add <dir> <request>').description('Write, validate and install a workflow node in a plugin')
89
+ .option('--kind <slug>', 'Workflow node kind')
90
+ .option('--yes', 'Accept requested capabilities')
91
+ .option('--json', 'Print the result as one JSON line')
92
+ .action(async (dir: string, request: string, opts: { kind?: string; yes?: boolean; json?: boolean }) => {
93
+ try {
94
+ const result = await addAndInstallNode({ dir, request, kind: opts.kind, yes: opts.yes,
95
+ deps: { consent: capabilities => askConsent(capabilities, !!opts.json) } });
96
+ if (opts.json) stdout.write(JSON.stringify(result) + '\n');
97
+ else {
98
+ console.log(`작성 ${result.timings.write}ms · 검증 ${result.timings.validate}ms${result.timings.repair === undefined ? '' : ` · 수리 ${result.timings.repair}ms`}`);
99
+ console.log(`설치 ${result.plugin ? '완료' : result.status === 'failed' && result.timings.install === 0 ? '미실행' : '실패'} · ${result.timings.install}ms`);
100
+ console.log(`동기화 ${result.status === 'installed' ? '완료' : result.plugin ? '실패' : '미실행'} · ${result.timings.sync}ms`);
101
+ for (const error of result.errors) console.error(error);
102
+ }
103
+ if (result.status === 'failed') process.exitCode = 1;
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ if (opts.json) stdout.write(JSON.stringify({ status: 'failed', errors: [message] }) + '\n');
107
+ else console.error(`plugin node add failed: ${message}`);
108
+ process.exitCode = 1;
109
+ }
110
+ });
86
111
  plugin.command('add <spec>').description('Install a local path, pinned git plugin or signed market plugin')
87
112
  .option('--yes', 'Accept requested capabilities')
88
113
  .option('--json', 'NDJSON progress events')
package/src/cli/pr-cli.ts CHANGED
@@ -17,6 +17,7 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
17
17
  import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
18
18
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
19
19
  import { runPublicLeakGate } from '../../scripts/ci-public-leak-gate.js';
20
+ import { exportLeakCheck, type ExportLeakCheck } from '../../scripts/ci-public-export-leak-gate.js';
20
21
  import { checkCommands, extractElanousCommands, type Finding as DocsCliFinding } from '../../scripts/docs-cli-check.js';
21
22
  import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
22
23
  import { isGoalDocumentFileName } from '../self-implement/goal-document.js';
@@ -68,6 +69,8 @@ export interface PrLandOpts {
68
69
  excludeActiveRunFiles?: boolean;
69
70
  excludeActiveRunSources?: boolean;
70
71
  landReason?: string;
72
+ /** Land even though changed files would leak into the public export (the reason goes in --land-reason). */
73
+ allowPublicLeak?: boolean;
71
74
  }
72
75
 
73
76
  export interface PrGranularityOpts {
@@ -101,6 +104,8 @@ export interface PrLandDeps {
101
104
  runDaemonPortGate?: (out: { log: (message: string) => void; error: (message: string) => void }) => boolean;
102
105
  /** 공개 유출 래칫(경고 전용) — 반환 0 통과 · 1 늘었다 · 2 못 쟀다. */
103
106
  runPublicLeakGate?: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number;
107
+ /** Test seam — the post-transform export leak check on changed files. */
108
+ runExportLeakCheck?: (changedFiles: readonly string[], cwd: string) => ExportLeakCheck;
104
109
  /** 공개 문서의 `elanous …` 호출 ↔ 실제 `--help` 대조(경고 전용) — 바뀐 공개 문서 경로를 받아 어긋남 목록을 돌려준다. */
105
110
  runDocsCliCheck?: (docFiles: readonly string[]) => DocsCliFinding[];
106
111
  /** 변경 시험 파일 간 간섭 검사 심(시험 주입용). */
@@ -269,6 +274,35 @@ function runPrLandPublicLeakGate(changedFiles: readonly string[], out: { log: (m
269
274
  return runPublicLeakGate({ args: ['--changed-files', ...changedFiles], log: out.log, error: out.log });
270
275
  }
271
276
 
277
+ /** The prepare check on this landing's changed files — scripts/ci-public-export-leak-gate.ts. */
278
+ export function runPrLandExportLeakCheck(changedFiles: readonly string[], cwd: string): ExportLeakCheck {
279
+ const top = runGitCommand(cwd, ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' });
280
+ return exportLeakCheck(changedFiles, top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd);
281
+ }
282
+
283
+ /** Blocks the landing when changed files would leak into the public export, unless allowPublicLeak. Returns false to stop. */
284
+ export function exportLeakBlock(
285
+ changedFiles: readonly string[] | undefined,
286
+ check: () => ExportLeakCheck,
287
+ allow: boolean,
288
+ out: { log: (message: string) => void; error: (message: string) => void },
289
+ ): boolean {
290
+ if (!changedFiles || changedFiles.length === 0) return true;
291
+ let result: ExportLeakCheck;
292
+ try { result = check(); } catch (error) { result = { measured: false, hits: [], detail: error instanceof Error ? error.message : String(error) }; }
293
+ record('public-export-leak', result.measured ? result.hits.length === 0 || allow : true, { measured: result.measured, hits: result.hits.length, allowed: allow && result.hits.length > 0 });
294
+ if (!result.measured) { out.log(`⚠ public-export-leak: 못 쟀다(${result.detail ?? '?'}) — 착지는 막지 않는다. 확인: bun scripts/public-export.ts --leak-check --files <파일…>`); return true; }
295
+ if (result.hits.length === 0) { out.log('✓ public-export-leak: 바뀐 파일이 공개본에 사적 흔적을 싣지 않는다(prepare 와 같은 변환 뒤).'); return true; }
296
+ const write = allow ? out.log : out.error;
297
+ write(`${allow ? '⚠' : '✗'} public-export-leak: 바뀐 파일 ${result.hits.length}곳이 공개본에 사적 흔적을 싣는다 — 다음 release prepare 가 여기서 실패한다:`);
298
+ for (const h of result.hits.slice(0, 20)) write(` ${h.file}:${h.line} ${h.marker}`);
299
+ if (result.hits.length > 20) write(` … 외 ${result.hits.length - 20}곳`);
300
+ write(' 확인: bun scripts/public-export.ts --leak-check --all --files <파일…>');
301
+ if (allow) { out.log(' --allow-public-leak 로 넘긴다(이유는 --land-reason).'); return true; }
302
+ out.error(' 고치거나, 공개본과 무관한 것이 확실하면 --allow-public-leak --land-reason "<이유>" 로 넘긴다.');
303
+ return false;
304
+ }
305
+
272
306
  export function publicLeakWarning(
273
307
  changedFiles: readonly string[] | undefined,
274
308
  gate: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number,
@@ -1286,6 +1320,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1286
1320
 
1287
1321
  const changedPaths = currentChangePaths(run, cwd, staged, base, out);
1288
1322
  publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? runPrLandPublicLeakGate, out);
1323
+ if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out)) return 1;
1289
1324
  if (publicDocPaths(changedPaths, () => true).length > 0) {
1290
1325
  const top = run('git', ['rev-parse', '--show-toplevel'], { cwd });
1291
1326
  const docsRoot = top.ok && top.out.trim() ? top.out.trim() : cwd;
@@ -1549,6 +1584,7 @@ export function registerPrCommands(program: Command, deps: PrLandDeps = {}): voi
1549
1584
  .option('--exclude-active-run-files', '도는 런과 겹친 파일을 이번 착지에서 제외')
1550
1585
  .option('--exclude-active-run-sources', '⚠ 위험: --exclude-active-run-files 가 골 문서가 아닌 파일(소스)을 빼도 착지를 강행')
1551
1586
  .option('--land-reason <text>', '권고를 보고도 지금 내는 이유(예: 남이 기다리는 차단 해제)')
1587
+ .option('--allow-public-leak', '바뀐 파일이 공개본에 사적 흔적을 실어도 착지(이유는 --land-reason)')
1552
1588
  .action(async (opts: PrLandOpts) => {
1553
1589
  // ⛔⭐⭐⭐ **관측 sink 를 먼저 건다** — 라이브 도그푸드가 잡은 결함(2026-08-03).
1554
1590
  // 단위 테스트는 `debug.log` 가 **불렸다**를 단언하지만, standalone CLI 는 sink 를 등록하지
@@ -239,6 +239,9 @@ export function registerProviderCommands(program: Command): void {
239
239
  // ⛔⭐ 「후보」는 판정기가 «자기 자신을 뺀» 것이다 — 표면이 현재 계정을 후보로 보여 주면
240
240
  // ***있지도 않은 선택지를 말한다***(리뷰 must-fix). 판정기와 같은 기준(storeKey)으로 거른다.
241
241
  const shownCandidates = s.candidates.filter((c) => c.storeKey !== s.current.storeKey);
242
+ const { codexCreditPlanFromDisk } = await import('./codex-credit-plan-view.js');
243
+ const { formatCodexCreditPlan } = await import('../budget/codex-credit-plan.js');
244
+ const creditPlan = codexCreditPlanFromDisk(now);
242
245
  const ageMinOf = (home: string | undefined): number | null => {
243
246
  if (!home) return null;
244
247
  const at = home === s.currentHome ? s.currentObservedAt : s.observedAtByHome[home];
@@ -258,6 +261,7 @@ export function registerProviderCommands(program: Command): void {
258
261
  if (opts.json) {
259
262
  await writeStdoutJson(JSON.stringify({
260
263
  policy: { value: s.policy.policy, source: s.policy.source },
264
+ creditPlan,
261
265
  universe: {
262
266
  instanceRoot, signalDir, authStore: authStorePath(),
263
267
  // ⭐ JSON 에도 싣는다 — 화면만 알면 스크립트가 못 센다
@@ -303,6 +307,7 @@ export function registerProviderCommands(program: Command): void {
303
307
  const policySource = s.policy.source === 'legacy-credits' ? ' · 옛 codexCreditsAllowed 에서'
304
308
  : s.policy.source === 'default' ? ' · 기본값' : '';
305
309
  console.log(`정책 ${s.policy.policy} (${CODEX_QUOTA_POLICY_LABEL[s.policy.policy]} · llm.codexQuotaPolicy)${policySource}`);
310
+ for (const line of formatCodexCreditPlan(creditPlan)) console.log(line);
306
311
  console.log(`지금 계정 ${s.current.name} (source=${s.current.source})`);
307
312
  console.log(` 홈=${s.currentHome ?? '(모름)'} 사용=${s.currentUsedPercent ?? '?'}% 찼나=${s.currentReached ?? '모름'} 신호=${ageOf(s.currentHome)} 크레딧=${creditText(s.currentCreditBalance, s.currentHasCredits)}`);
308
313
  console.log('후보');
@@ -5,6 +5,7 @@
5
5
  // elanous release tag --version <x.y.z> --source <commit> [--yes]
6
6
  // elanous release verify [--version <x.y.z>]
7
7
  // elanous release notes --from <ref> [--to <ref>]
8
+ // elanous release run --version <x.y.z> [--dry-run] [--json] [--if-ready]
8
9
  //
9
10
  // 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
10
11
  // PWA 빌드(빌드 산출은 공개본에 안 실린다 → 빠뜨리면 «웹 화면 없는 판»). 절차를 명령으로 굳힌다.
@@ -24,6 +25,9 @@ import { envLiteral } from '../platform/env-literal.js';
24
25
  import { userConfigPath } from '../user-config.js';
25
26
  import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition } from '../release-loop/checklist.js';
26
27
  import { writeStdoutJson } from './stdout-json.js';
28
+ import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
29
+ import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
30
+ import { runReleaseIfReady } from './release-run-if-ready.js';
27
31
 
28
32
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
29
33
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -482,7 +486,7 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
482
486
  }
483
487
  }
484
488
 
485
- export function registerReleaseCommands(program: Command): void {
489
+ export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}): void {
486
490
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
487
491
  const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
488
492
  .option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
@@ -587,4 +591,41 @@ export function registerReleaseCommands(program: Command): void {
587
591
  const { draftReleaseNotes, readLandedCommits, renderReleaseNotes } = await import('../../scripts/release-notes.js');
588
592
  console.log(renderReleaseNotes(draftReleaseNotes(readLandedCommits(o.from, o.to), o.from, o.to)));
589
593
  });
594
+ release.command('run')
595
+ .description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
596
+ .requiredOption('--version <v>', '공개할 판(x.y.z)')
597
+ .option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
598
+ .option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
599
+ .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
600
+ .action(async (o: { version: string; dryRun?: boolean; ifReady?: boolean; json?: boolean }) => {
601
+ if (o.ifReady && !o.dryRun) {
602
+ try {
603
+ const outcome = await runReleaseIfReady(o.version, {
604
+ ledgerRoot: releaseRunDeps.ledgerRoot,
605
+ readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
606
+ run: () => runUnattendedRelease({ version: o.version }, releaseRunDeps),
607
+ });
608
+ if (outcome.skipped) {
609
+ if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
610
+ else console.log(`· 준비 안 됨 ${o.version} · ${outcome.reason} · ${outcome.detail}`);
611
+ return;
612
+ }
613
+ const result = outcome.result;
614
+ const ok = result.dryRun || result.state?.status === 'done' || result.state?.status === 'awaiting-approval';
615
+ (o.json ? console.error : console.log)(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
616
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
617
+ if (!ok && !process.exitCode) process.exitCode = 1;
618
+ } catch (e) {
619
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
620
+ else console.error(`⛔ ${(e as Error).message}`);
621
+ process.exitCode = 1;
622
+ }
623
+ return;
624
+ }
625
+ await jsonAction(o.json, async (log) => {
626
+ const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun }, releaseRunDeps);
627
+ log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
628
+ return result;
629
+ }, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
630
+ });
590
631
  }
@@ -0,0 +1,48 @@
1
+ import { closeSync, mkdirSync, openSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+ import { releaseLedgerRoot } from '../instance/resolve.js';
5
+ import type { ReleaseReadiness } from '../../scripts/release-loop/release-readiness.js';
6
+
7
+ export type ReleaseIfReadySkip = { skipped: true; reason: Exclude<ReleaseReadiness, { ready: true }>['reason']; detail: string };
8
+
9
+ export async function runReleaseIfReady<T>(version: string, deps: {
10
+ readiness: (version: string, options?: { isPidAlive?: (pid: number) => boolean }) => ReleaseReadiness | Promise<ReleaseReadiness>;
11
+ run: () => Promise<T>;
12
+ ledgerRoot?: string;
13
+ pid?: number;
14
+ now?: () => Date;
15
+ }): Promise<ReleaseIfReadySkip | { skipped: false; result: T }> {
16
+ const skip = (readiness: Exclude<ReleaseReadiness, { ready: true }>): ReleaseIfReadySkip => {
17
+ const detail = readiness.reason === 'checklist-blocked'
18
+ ? [...readiness.details.red, ...readiness.details.undecided, ...readiness.details.blocked].join(', ')
19
+ : readiness.details;
20
+ debug.log('release-loop.readiness', readiness.reason, { version, detail });
21
+ return { skipped: true, reason: readiness.reason, detail };
22
+ };
23
+ const readiness = await deps.readiness(version);
24
+ if (!readiness.ready) return skip(readiness);
25
+
26
+ const dir = join(deps.ledgerRoot ?? releaseLedgerRoot(), 'release', version);
27
+ mkdirSync(dir, { recursive: true });
28
+ const lock = join(dir, 'run.lock');
29
+ let fd: number;
30
+ try { fd = openSync(lock, 'wx', 0o600); }
31
+ catch (error) {
32
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
33
+ const detail = 'run.lock already exists';
34
+ debug.log('release-loop.readiness', 'already-running', { version, detail });
35
+ return { skipped: true, reason: 'already-running', detail };
36
+ }
37
+ try {
38
+ writeFileSync(fd, `${JSON.stringify({ pid: deps.pid ?? process.pid, startedAt: (deps.now ?? (() => new Date()))().toISOString() })}\n`);
39
+ // This call still checks publication and checklist, but must not treat our own lock as another runner.
40
+ const insideLock = await deps.readiness(version, { isPidAlive: () => false });
41
+ if (!insideLock.ready) return skip(insideLock);
42
+ debug.log('release-loop.readiness', 'ready', { version });
43
+ return { skipped: false, result: await deps.run() };
44
+ } finally {
45
+ closeSync(fd);
46
+ rmSync(lock);
47
+ }
48
+ }
@@ -1,9 +1,10 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
- import type { Command } from 'commander';
5
- import { formatDoctorReport, runDoctor, type DoctorOptions, type DoctorReport } from './doctor-cli.js';
4
+ import { Command } from 'commander';
5
+ import { formatDoctorReport, registerDoctorCommand, runDoctor, type DoctorOptions, type DoctorReport } from './doctor-cli.js';
6
6
  import { applyClaudePluginSetup, planClaudePluginSetup } from './claude-plugin-setup.js';
7
+ import { recommendedSetup } from './setup-recommend.js';
7
8
  import { debug } from '../debug/log.js';
8
9
  import { getUserConfig, saveUserConfig, type UserConfig } from '../user-config.js';
9
10
 
@@ -20,6 +21,7 @@ type SetupStep = {
20
21
 
21
22
  export interface SetupCliDeps extends DoctorOptions {
22
23
  runDoctor?: (options: DoctorOptions) => DoctorReport;
24
+ runDoctorFix?: (args: readonly string[]) => Promise<number>;
23
25
  readFile?: (path: string) => string;
24
26
  exists?: (path: string) => boolean;
25
27
  homeDir?: () => string;
@@ -154,14 +156,16 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
154
156
  const setup = program.command('setup')
155
157
  .description('Check OpenAI Codex setup and guide each missing credential step')
156
158
  .option('--non-interactive', 'Report setup state without prompts or writes')
157
- .action(async (opts: { nonInteractive?: boolean }) => {
159
+ .option('--yes', 'Set a missing Codex provider and apply recommended doctor repairs without asking')
160
+ .action(async (opts: { nonInteractive?: boolean; yes?: boolean }) => {
158
161
  const doctor = doctorRunner(deps);
159
162
  if (opts.nonInteractive) {
160
163
  out.log(formatSetupReport(doctor, setupSteps(deps), true));
164
+ for (const line of recommendedSetup((deps.getUserConfig ?? getUserConfig)())) out.log(line);
161
165
  setExitCode(0);
162
166
  return;
163
167
  }
164
- if (!isStdinTty()) {
168
+ if (!isStdinTty() && !opts.yes) {
165
169
  (out.error ?? out.log)('대화형 온보딩은 stdin TTY가 있는 자리에서만 실행할 수 있다. 무인 설정은 `elanous setup --non-interactive`를 사용하라.');
166
170
  setExitCode(1);
167
171
  return;
@@ -169,8 +173,8 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
169
173
  let steps = setupSteps(deps);
170
174
  const provider = steps[1]!;
171
175
  if (!provider.complete) {
172
- const answer = await prompt('Set llm.provider to openai-codex now? [y/N] ');
173
- if (answer?.trim().toLowerCase() === 'y') {
176
+ const approved = opts.yes || (await prompt('Set llm.provider to openai-codex now? [y/N] '))?.trim().toLowerCase() === 'y';
177
+ if (approved) {
174
178
  const config = (deps.getUserConfig ?? getUserConfig)();
175
179
  (deps.saveUserConfig ?? saveUserConfig)({ ...config, llm: { ...config.llm, provider: 'openai-codex' } });
176
180
  steps = setupSteps(deps);
@@ -179,6 +183,23 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
179
183
  }
180
184
  }
181
185
  out.log(formatSetupReport(doctor, steps, false));
186
+ for (const line of recommendedSetup((deps.getUserConfig ?? getUserConfig)())) out.log(line);
187
+ const accepted = opts.yes || (isStdinTty() && /^(?:y|yes)?$/i.test((await prompt('추천대로 켤까요? [Y/n] '))?.trim() ?? 'n'));
188
+ if (accepted) {
189
+ const runDoctorFix = deps.runDoctorFix ?? (async (args: readonly string[]) => {
190
+ const doctorProgram = new Command();
191
+ let exitCode = 0;
192
+ registerDoctorCommand(doctorProgram, {
193
+ out,
194
+ err: { error: (message) => (out.error ?? out.log)(message) },
195
+ setExitCode: (code) => { exitCode = code; },
196
+ });
197
+ await doctorProgram.parseAsync([...args], { from: 'user' });
198
+ return exitCode;
199
+ });
200
+ setExitCode(await runDoctorFix(['doctor', '--fix', '--yes']));
201
+ return;
202
+ }
182
203
  setExitCode(0);
183
204
  });
184
205
 
@@ -0,0 +1,8 @@
1
+ export function recommendedSetup(state: { chat: { fastPath: boolean } }): [string, string] {
2
+ return [
3
+ '고칠 것 고치기 — 권한·PATH·서비스 파일·파이썬 환경 등 고칠 수 있는 항목을 고칩니다(로그인·서비스 재시작은 별도): `elanous doctor --fix --yes`',
4
+ state.chat.fastPath
5
+ ? '짧은 물음은 빠르게 — 켜져 있음(chat.fastPath=true): 짧은 물음에 도구 없이 먼저 답합니다. 끄기: `elanous config set chat.fastPath false`'
6
+ : '짧은 물음은 빠르게 — 꺼져 있음(chat.fastPath=false): 켜면 짧은 물음에 도구 없이 먼저 답합니다. 켜기: `elanous config set chat.fastPath true`',
7
+ ];
8
+ }
@@ -12,9 +12,13 @@ import { consumeCodexResetCredits, listCodexResetCredits } from '../budget/codex
12
12
  import { rollupRunUsage, type RunUsageInput } from '../budget/run-usage-rollup.js';
13
13
  import { LogStore, logsDbPath, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
14
14
  import { LOGS_SINCE_OPTION, parseSince } from './logs-cli.js';
15
+ import { formatCodexCreditPlan, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
16
+ import { codexCreditPlanFromDisk } from './codex-credit-plan-view.js';
15
17
 
16
18
  export interface UsageCliDeps {
17
19
  readonly collect?: typeof collectUnifiedUsage;
20
+ /** Codex credit plan (disk only). Returns null when it cannot be read. */
21
+ readonly creditPlan?: () => CodexCreditPlan | null;
18
22
  readonly out?: { log: (s: string) => void };
19
23
  /** Read-only LogStore query seam; returns stored llm-usage rows, newest first. */
20
24
  readonly readRunLogs?: (query: LogQuery) => LogStoreRow[];
@@ -61,6 +65,10 @@ function usageData(row: LogStoreRow): RunUsageInput | null {
61
65
  } catch { return null; }
62
66
  }
63
67
 
68
+ function defaultCreditPlan(): CodexCreditPlan | null {
69
+ try { return codexCreditPlanFromDisk(); } catch { return null; }
70
+ }
71
+
64
72
  export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}): void {
65
73
  const out = deps.out ?? { log: (s: string) => console.log(s) };
66
74
  const collect = deps.collect ?? collectUnifiedUsage;
@@ -71,11 +79,13 @@ export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}):
71
79
  .option('--json', 'JSON 출력 — 자격 값은 없다')
72
80
  .action(async (opts: { json?: boolean }) => {
73
81
  const report = await collect();
82
+ const creditPlan = (deps.creditPlan ?? defaultCreditPlan)();
74
83
  if (opts.json) {
75
- out.log(JSON.stringify(report, null, 2));
84
+ out.log(JSON.stringify({ ...report, codexCreditPlan: creditPlan }, null, 2));
76
85
  return;
77
86
  }
78
87
  out.log(formatUnifiedUsage(report));
88
+ if (creditPlan) out.log(['', 'codex 크레딧', ...formatCodexCreditPlan(creditPlan).map((l) => ` ${l}`)].join('\n'));
79
89
  });
80
90
 
81
91
  usage
@@ -62,14 +62,33 @@ export interface CodexSetupResult {
62
62
 
63
63
  // ── Picker helpers (shared with onboarding) ──────────────────────────
64
64
 
65
- export async function pickCodexAuthMode(io: WizardIO): Promise<CodexAuthMode> {
66
- const existing = loadTokens('openai-codex');
67
- if (existing) {
68
- const exp = existing.tokens.expiresAt;
69
- const when = exp != null
70
- ? (Date.now() > exp ? 'EXPIRED' : `${Math.round((exp - Date.now()) / 60000)}min left`)
71
- : 'no expiry tracked';
72
- io.print(` OAuth tokens already on file (${when}).`);
65
+ export interface CodexAuthModeDeps {
66
+ loadTokens?: typeof loadTokens;
67
+ /** OB1 — a codex CLI login (~/.codex/auth.json) the runtime already uses; default = `inspectSubscription`. */
68
+ hasCodexCliLogin?: () => boolean;
69
+ }
70
+
71
+ export async function pickCodexAuthMode(io: WizardIO, deps: CodexAuthModeDeps = {}): Promise<CodexAuthMode> {
72
+ const existing = (deps.loadTokens ?? loadTokens)('openai-codex');
73
+ // OB1 (10-01): a user already signed in to the codex CLI was sent through the device-code flow again,
74
+ // because only elanous's own token store counted. The runtime reads ~/.codex/auth.json, so it counts too.
75
+ // Same judgment as the runtime (`codexOAuthAvailable` in llm.ts) — two judgments drifting apart was the bug.
76
+ const codexCli = !existing && (deps.hasCodexCliLogin ?? (() => {
77
+ try {
78
+ const { codexOAuthAvailable } = require('../llm.js') as typeof import('../llm.js');
79
+ return codexOAuthAvailable();
80
+ } catch { return false; }
81
+ }))();
82
+ if (existing || codexCli) {
83
+ if (existing) {
84
+ const exp = existing.tokens.expiresAt;
85
+ const when = exp != null
86
+ ? (Date.now() > exp ? 'EXPIRED' : `${Math.round((exp - Date.now()) / 60000)}min left`)
87
+ : 'no expiry tracked';
88
+ io.print(` OAuth tokens already on file (${when}).`);
89
+ } else {
90
+ io.print(' Found your ChatGPT/Codex login from the codex CLI (~/.codex/auth.json).');
91
+ }
73
92
  // Sprint 11 — chooseFrom Yes/No so fullScreenIO renders the arrow
74
93
  // picker (with 'y'/'n' + 한글 자모 quick-pick from PR #986 + #988)
75
94
  // instead of a bare text input. The runner distinguishes 'oauth-keep'
@@ -16779,7 +16779,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
16779
16779
  preferHistoryArrowKeys: slashQuickMode,
16780
16780
  history: inputHistory,
16781
16781
  placeholder: anyProviderAvailable()
16782
- ? '/command, or type a question (inline /path/to.pdf to attach)'
16782
+ ? '말 한 줄로 시작 — 질문하거나 «하니스로 구현 …» · /command · /path/to.pdf 첨부'
16783
16783
  : '/quit /clear (run `elanous setup` — no LLM provider available)',
16784
16784
  debugLog: debug.enabled ? (event, label, payload) => debug.log(event, label, payload) : undefined,
16785
16785
  textInputOpts: {
package/src/discord.ts CHANGED
@@ -23,6 +23,8 @@
23
23
  import { markdownToTelegramHtml as _unusedMarkdownFn } from './telegram-format.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
24
24
  import { debug } from './debug/log.js';
25
25
  import { getUserConfig } from './user-config.js';
26
+ import { handleDiscordSeatWork, type DiscordSeatWorkDeps } from './intake-plane/discord-seat-work.js';
27
+ import { parseSeatAddress } from './seat-address/seat-address.js';
26
28
 
27
29
  // Telegram's HTML markdown isn't directly useful for Discord (Discord
28
30
  // uses CommonMark + its own subset). Import kept as a reminder that
@@ -163,6 +165,8 @@ export interface DiscordBotOpts {
163
165
  * primary `onMessage` LLM chat path. Errors are swallowed by the
164
166
  * caller — this is a best-effort fan-out. */
165
167
  onTriggerTap?: (event: DcTriggerEvent) => void | Promise<void>;
168
+ /** Override the intake door's dependencies for isolated Discord seat-work tests. */
169
+ seatWorkDeps?: DiscordSeatWorkDeps;
166
170
  }
167
171
 
168
172
  /** Surface-unification v2 — minimal trigger-side event shape. Maps
@@ -204,6 +208,8 @@ export class DiscordBot {
204
208
  private readonly token: string;
205
209
  private readonly allowedUsers: Set<string>;
206
210
  private readonly guildTextChannels: Set<string>;
211
+ /** The bot's own user id (from READY) — EV12c lets an allowlisted @mention open any guild channel. */
212
+ private selfUserId: string | undefined;
207
213
  private readonly onMessage: DcMessageHandler;
208
214
  private readonly maxChars: number;
209
215
  private readonly log: (msg: string) => void;
@@ -216,6 +222,7 @@ export class DiscordBot {
216
222
  private readonly onReaction: ((event: DcReactionEvent) => void | Promise<void>) | null;
217
223
  /** Surface-unification v2 (FU-1) — workflow-runtime tap. */
218
224
  private readonly onTriggerTap: ((event: DcTriggerEvent) => void | Promise<void>) | null;
225
+ private readonly seatWorkDeps: DiscordSeatWorkDeps;
219
226
  private ws: WebSocket | null = null;
220
227
  private running = false;
221
228
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
@@ -239,6 +246,7 @@ export class DiscordBot {
239
246
  this.onInteraction = opts.onInteraction ?? null;
240
247
  this.onReaction = opts.onReaction ?? null;
241
248
  this.onTriggerTap = opts.onTriggerTap ?? null;
249
+ this.seatWorkDeps = opts.seatWorkDeps ?? {};
242
250
  }
243
251
 
244
252
  /** Send a raw payload on the gateway WebSocket. Voice adapter uses
@@ -604,6 +612,7 @@ export class DiscordBot {
604
612
  session_id?: string;
605
613
  };
606
614
  this.log(`READY — logged in as @${d.user?.username ?? '?'} (${d.user?.id ?? '?'})`);
615
+ if (d.user?.id) this.selfUserId = d.user.id;
607
616
  if (this.voiceTap?.onReady && d.session_id && d.user?.id) {
608
617
  try { this.voiceTap.onReady(d.session_id, d.user.id); }
609
618
  catch (err: any) { this.log(`voice tap onReady failed: ${err?.message ?? err}`); }
@@ -684,6 +693,20 @@ export class DiscordBot {
684
693
 
685
694
  // ── Message routing ────────────────────────────────────────
686
695
 
696
+ /** EV12c — true when this message @mentions the bot itself (mentions array or the raw `<@id>` token). */
697
+ private mentionsSelf(m: Record<string, unknown>, text: string): boolean {
698
+ const self = this.selfUserId;
699
+ if (!self) return false;
700
+ if (Array.isArray(m.mentions) && (m.mentions as Array<{ id?: unknown }>).some(u => String(u?.id) === self)) return true;
701
+ return text.includes(`<@${self}>`) || text.includes(`<@!${self}>`);
702
+ }
703
+
704
+ private stripSelfMention(text: string): string {
705
+ const self = this.selfUserId;
706
+ if (!self) return text;
707
+ return text.split(`<@!${self}>`).join('').split(`<@${self}>`).join('').replace(/\s+/g, ' ').trim();
708
+ }
709
+
687
710
  private async handleMessageCreate(m: Record<string, unknown>): Promise<void> {
688
711
  // Ignore bot's own messages (DM reply from our own send would
689
712
  // re-enter here).
@@ -692,19 +715,25 @@ export class DiscordBot {
692
715
 
693
716
  // DM-only v1 scope: guild_id is absent for DM channels.
694
717
  // Voice channel commands (`/voice-join`, `/voice-leave`,
695
- // `/voice-status`) intentionally bypass DM-only — they need a
696
- // guild context to operate. Everything else stays DM-routed so
697
- // unrelated guild traffic doesn't reach the LLM path.
718
+ // `/voice-status`) and allowlisted, seat-addressed work bypass
719
+ // DM-only. All other guild traffic stays gated unless explicitly
720
+ // configured as a guild text channel.
698
721
  const isDm = !m.guild_id;
722
+ const rawText = typeof m.content === 'string' ? m.content : '';
723
+ const mentionsSelf = this.mentionsSelf(m, rawText);
699
724
  if (!isDm && !this.guildTextChannels.has(String(m.channel_id))) {
700
- const trimmed = typeof m.content === 'string' ? m.content.trim() : '';
701
- if (!/^\/voice-(join|leave|status)\b/.test(trimmed)) return;
725
+ if (!/^\/voice-(join|leave|status)\b/.test(rawText.trim())
726
+ && !(this.allowedUsers.has(author.id) && (parseSeatAddress(rawText) || mentionsSelf))) {
727
+ debug.log('discord.trigger', 'dropped', { reason: 'guild-not-chat', channelId: String(m.channel_id) });
728
+ return;
729
+ }
702
730
  }
703
731
 
704
732
  // Allowlist enforcement. An empty allowlist refuses everyone —
705
733
  // safer default than "open to the world" when the bot runs
706
734
  // with no user config.
707
735
  if (this.allowedUsers.size > 0 && !this.allowedUsers.has(author.id)) {
736
+ debug.log('discord.trigger', 'dropped', { reason: 'not-allowed', isDm });
708
737
  this.log(`discord: refusing unknown user ${author.id}`);
709
738
  try {
710
739
  await this.sendMessage(
@@ -738,7 +767,7 @@ export class DiscordBot {
738
767
  channelId: String(m.channel_id),
739
768
  userId: author.id,
740
769
  userName: author.username,
741
- text: typeof m.content === 'string' ? m.content : '',
770
+ text: mentionsSelf ? this.stripSelfMention(rawText) : rawText,
742
771
  messageId: String(m.id),
743
772
  isDm,
744
773
  attachments,
@@ -790,6 +819,27 @@ export class DiscordBot {
790
819
  }
791
820
  }
792
821
 
822
+ // Addressed work is submitted only by configured owners; ordinary chat keeps its existing path.
823
+ if (this.allowedUsers.has(author.id) && parseSeatAddress(ctx.text)) {
824
+ const threadId = typeof m.thread_id === 'string' && m.thread_id ? m.thread_id : undefined;
825
+ const replyChannelId = threadId ?? ctx.channelId;
826
+ try {
827
+ const seatReply = await handleDiscordSeatWork(ctx.text, {
828
+ channelId: replyChannelId,
829
+ messageId: ctx.messageId,
830
+ ...(threadId ? { threadId } : {}),
831
+ }, this.seatWorkDeps);
832
+ if (seatReply !== null) {
833
+ await this.sendMessage(replyChannelId, seatReply);
834
+ return;
835
+ }
836
+ } catch (err: unknown) {
837
+ this.log(`discord seat work failed: ${err instanceof Error ? err.message : String(err)}`);
838
+ await this.sendMessage(replyChannelId, '좌석 요청을 접수하지 못했습니다. 잠시 후 다시 보내 주세요.');
839
+ return;
840
+ }
841
+ }
842
+
793
843
  // §C5 스트리밍 flip — streaming.discord ON 이면 옛 경로(placeholder+streamer+delivery) 억제,
794
844
  // 청크 fan-out sink 가 담당(handler chunkProducer 가 primarySurfaces=['discord']). 텔레그램 동형.
795
845
  const dcStreamingFlip = getUserConfig().sessionFabric?.streaming?.discord === true;