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
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
@@ -490,6 +498,23 @@ export class DiscordBot {
490
498
  return r?.id ? { id: r.id } : null;
491
499
  }
492
500
 
501
+ /** DEC-TG — edit a message's text AND components (an empty list removes the buttons). */
502
+ async editMessageWithComponents(
503
+ channelId: string,
504
+ messageId: string,
505
+ text: string,
506
+ components: ReadonlyArray<Record<string, unknown>>,
507
+ ): Promise<void> {
508
+ const content = text.length > this.maxChars ? text.slice(0, this.maxChars - 4) + ' …' : text;
509
+ await this.restCall('PATCH', `/channels/${channelId}/messages/${messageId}`, { content, components });
510
+ }
511
+
512
+ /** DEC-TG — the DM channel id with one user (Discord returns the existing one when it already exists). */
513
+ async openDmChannel(userId: string): Promise<string | null> {
514
+ const r = await this.restCall<{ id?: string }>('POST', '/users/@me/channels', { recipient_id: userId });
515
+ return r?.id ?? null;
516
+ }
517
+
493
518
  // ── Gateway: connect + run forever ──────────────────────────
494
519
 
495
520
  /** Connect to the Gateway, identify, and process events until
@@ -604,6 +629,7 @@ export class DiscordBot {
604
629
  session_id?: string;
605
630
  };
606
631
  this.log(`READY — logged in as @${d.user?.username ?? '?'} (${d.user?.id ?? '?'})`);
632
+ if (d.user?.id) this.selfUserId = d.user.id;
607
633
  if (this.voiceTap?.onReady && d.session_id && d.user?.id) {
608
634
  try { this.voiceTap.onReady(d.session_id, d.user.id); }
609
635
  catch (err: any) { this.log(`voice tap onReady failed: ${err?.message ?? err}`); }
@@ -684,6 +710,20 @@ export class DiscordBot {
684
710
 
685
711
  // ── Message routing ────────────────────────────────────────
686
712
 
713
+ /** EV12c — true when this message @mentions the bot itself (mentions array or the raw `<@id>` token). */
714
+ private mentionsSelf(m: Record<string, unknown>, text: string): boolean {
715
+ const self = this.selfUserId;
716
+ if (!self) return false;
717
+ if (Array.isArray(m.mentions) && (m.mentions as Array<{ id?: unknown }>).some(u => String(u?.id) === self)) return true;
718
+ return text.includes(`<@${self}>`) || text.includes(`<@!${self}>`);
719
+ }
720
+
721
+ private stripSelfMention(text: string): string {
722
+ const self = this.selfUserId;
723
+ if (!self) return text;
724
+ return text.split(`<@!${self}>`).join('').split(`<@${self}>`).join('').replace(/\s+/g, ' ').trim();
725
+ }
726
+
687
727
  private async handleMessageCreate(m: Record<string, unknown>): Promise<void> {
688
728
  // Ignore bot's own messages (DM reply from our own send would
689
729
  // re-enter here).
@@ -692,19 +732,25 @@ export class DiscordBot {
692
732
 
693
733
  // DM-only v1 scope: guild_id is absent for DM channels.
694
734
  // 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.
735
+ // `/voice-status`) and allowlisted, seat-addressed work bypass
736
+ // DM-only. All other guild traffic stays gated unless explicitly
737
+ // configured as a guild text channel.
698
738
  const isDm = !m.guild_id;
739
+ const rawText = typeof m.content === 'string' ? m.content : '';
740
+ const mentionsSelf = this.mentionsSelf(m, rawText);
699
741
  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;
742
+ if (!/^\/voice-(join|leave|status)\b/.test(rawText.trim())
743
+ && !(this.allowedUsers.has(author.id) && (parseSeatAddress(rawText) || mentionsSelf))) {
744
+ debug.log('discord.trigger', 'dropped', { reason: 'guild-not-chat', channelId: String(m.channel_id) });
745
+ return;
746
+ }
702
747
  }
703
748
 
704
749
  // Allowlist enforcement. An empty allowlist refuses everyone —
705
750
  // safer default than "open to the world" when the bot runs
706
751
  // with no user config.
707
752
  if (this.allowedUsers.size > 0 && !this.allowedUsers.has(author.id)) {
753
+ debug.log('discord.trigger', 'dropped', { reason: 'not-allowed', isDm });
708
754
  this.log(`discord: refusing unknown user ${author.id}`);
709
755
  try {
710
756
  await this.sendMessage(
@@ -738,7 +784,7 @@ export class DiscordBot {
738
784
  channelId: String(m.channel_id),
739
785
  userId: author.id,
740
786
  userName: author.username,
741
- text: typeof m.content === 'string' ? m.content : '',
787
+ text: mentionsSelf ? this.stripSelfMention(rawText) : rawText,
742
788
  messageId: String(m.id),
743
789
  isDm,
744
790
  attachments,
@@ -790,6 +836,27 @@ export class DiscordBot {
790
836
  }
791
837
  }
792
838
 
839
+ // Addressed work is submitted only by configured owners; ordinary chat keeps its existing path.
840
+ if (this.allowedUsers.has(author.id) && parseSeatAddress(ctx.text)) {
841
+ const threadId = typeof m.thread_id === 'string' && m.thread_id ? m.thread_id : undefined;
842
+ const replyChannelId = threadId ?? ctx.channelId;
843
+ try {
844
+ const seatReply = await handleDiscordSeatWork(ctx.text, {
845
+ channelId: replyChannelId,
846
+ messageId: ctx.messageId,
847
+ ...(threadId ? { threadId } : {}),
848
+ }, this.seatWorkDeps);
849
+ if (seatReply !== null) {
850
+ await this.sendMessage(replyChannelId, seatReply);
851
+ return;
852
+ }
853
+ } catch (err: unknown) {
854
+ this.log(`discord seat work failed: ${err instanceof Error ? err.message : String(err)}`);
855
+ await this.sendMessage(replyChannelId, '좌석 요청을 접수하지 못했습니다. 잠시 후 다시 보내 주세요.');
856
+ return;
857
+ }
858
+ }
859
+
793
860
  // §C5 스트리밍 flip — streaming.discord ON 이면 옛 경로(placeholder+streamer+delivery) 억제,
794
861
  // 청크 fan-out sink 가 담당(handler chunkProducer 가 primarySurfaces=['discord']). 텔레그램 동형.
795
862
  const dcStreamingFlip = getUserConfig().sessionFabric?.streaming?.discord === true;
@@ -15,6 +15,8 @@ import { existsSync } from 'node:fs';
15
15
  import { Database } from 'bun:sqlite';
16
16
  import type { LLMToolSpec } from '../llm.js';
17
17
  import { debug } from '../debug/log.js';
18
+ import { runRecall, type RecallSearch } from '../tool-runtime/self-cognition-runtimes.js';
19
+ import type { ToolRuntimeContext } from '../tool-runtime/types.js';
18
20
  import { SCHEDULE_MANAGE_SPEC, dispatchScheduleManage } from './schedule-manage-tool.js';
19
21
  import { SESSION_MANAGE_SPEC, dispatchSessionQuery } from './session-query-tool.js';
20
22
  import { AUTOPILOT_MISSION_SPEC, dispatchAutopilotMissions } from '../autopilot/mission-tool.js';
@@ -43,6 +45,7 @@ const MEMORY_RECALL_SPEC: LLMToolSpec = {
43
45
  sinceHours: { type: 'number', description: '조회 기간 시간(기본 168=7일).' },
44
46
  limit: { type: 'number', description: '반환 건수(기본 8).' },
45
47
  restoreId: { type: 'string', description: '느린 복원(선택) — archived 후보의 id 를 지정하면 그 cold 기억을 S3 에서 복원(recall_count++·warm 재활성화)해 본문 반환. 회상 결과 archived 에 관련 후보가 있고 본문이 필요할 때만.' },
48
+ timeBudgetMs: { type: 'number', description: '기억 조회 시간 예산(밀리초·기본 60000). 초과 시 찾은 것까지 부분 반환.' },
46
49
  },
47
50
  required: [],
48
51
  },
@@ -50,7 +53,7 @@ const MEMORY_RECALL_SPEC: LLMToolSpec = {
50
53
 
51
54
  /** memory_recall 코어(db 주입·테스트 가능). restoreId 지정 시 느린 복원(축B P2), 아니면 회상 + archive 발견.
52
55
  * recency+importance+relevance 스코어. restoreDeps 는 테스트 S3 mock(생략 시 기본 S3). */
53
- export function recallOrRestore(mdb: Database, args: Record<string, unknown>, restoreDeps?: ArchiveS3Deps): unknown {
56
+ export function recallOrRestore(mdb: Database, args: Record<string, unknown>, restoreDeps?: ArchiveS3Deps, onPartial?: (result: Record<string, unknown>) => void): unknown {
54
57
  // ★ 축B P2 느린 복원 — restoreId 지정 시 cold 기억을 S3 에서 명시적 복원(recall_count++·warm 재활성화).
55
58
  // 자동 아님(명시적) = 복원 폭주 방지. 죽어있던 restoreFromArchive 호출부 배선.
56
59
  if (typeof args.restoreId === 'string' && args.restoreId) {
@@ -75,15 +78,17 @@ export function recallOrRestore(mdb: Database, args: Record<string, unknown>, re
75
78
  ...(typeof args.sinceHours === 'number' ? { sinceHours: args.sinceHours } : {}),
76
79
  ...(typeof args.limit === 'number' ? { limit: args.limit } : {}),
77
80
  });
81
+ const mappedHits = hits.map(h => ({
82
+ when: h.ts, surface: h.surface, kind: h.kind,
83
+ text: h.text.slice(0, 400), importance: h.importance,
84
+ score: Math.round(h.score * 1000) / 1000,
85
+ }));
86
+ onPartial?.({ hits: mappedHits, count: hits.length });
78
87
  // M2 — cold(S3 보관) 기억 메타 발견(본문 fetch 없음·빠름). 존재+id 알리고 복원은 restoreId 로 명시적.
79
88
  const q = typeof args.query === 'string' ? String(args.query) : '';
80
89
  const archived = q ? searchArchive(mdb, q, { ...(typeof args.domain === 'string' && args.domain ? { domain: String(args.domain) } : {}), limit: 3 }) : [];
81
90
  return {
82
- hits: hits.map(h => ({
83
- when: h.ts, surface: h.surface, kind: h.kind,
84
- text: h.text.slice(0, 400), importance: h.importance,
85
- score: Math.round(h.score * 1000) / 1000,
86
- })),
91
+ hits: mappedHits,
87
92
  count: hits.length,
88
93
  ...(archived.length ? { archived: archived.map(a => ({ id: a.id, when: a.ts, summary: a.summary, note: 'cold·S3 보관(restoreId 로 느린 복원)' })) } : {}),
89
94
  note: '내가 발송/통지한 것의 원장 회상(크로스서피스 기억·hot/warm). archived=흐려져 S3 로 이관된 cold 기억(존재+id·본문은 restoreId 로 느린 복원). 비었으면 해당 기간 발송 없음.',
@@ -91,18 +96,18 @@ export function recallOrRestore(mdb: Database, args: Record<string, unknown>, re
91
96
  }
92
97
 
93
98
  /** memory_recall 공유 구현(finance-tools 에서 이관). db 열기 + recallOrRestore 위임. */
94
- async function dispatchMemoryRecall(args: Record<string, unknown>): Promise<unknown> {
99
+ async function dispatchMemoryRecall(args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void): Promise<unknown> {
95
100
  if (!existsSync(surfaceEventsDbPath())) {
96
101
  return { hits: [], note: '발송 원장 미생성 — 아직 기록된 발송이 없음(데몬 재시작 후 sendOutbound부터 적재).' };
97
102
  }
98
103
  const mdb = openSurfaceEventsDb();
99
- try { return recallOrRestore(mdb, args); }
104
+ try { return recallOrRestore(mdb, args, undefined, onPartial); }
100
105
  catch (e) { return { error: `기억 회상 실패: ${e instanceof Error ? e.message.slice(0, 100) : String(e)}` }; }
101
106
  finally { mdb.close(); }
102
107
  }
103
108
 
104
109
  /** L2 코어 앱 도구 dispatch 핸들러 — 이름 → 구현. 새 코어 도구는 여기 추가. */
105
- const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
110
+ const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void) => Promise<unknown>> = {
106
111
  schedule_manage: dispatchScheduleManage,
107
112
  session_manage: (args) => dispatchSessionQuery(args), // 대화 세션 검색·열람·목록·삭제(내용/ID/텔레그램)
108
113
  memory_recall: dispatchMemoryRecall,
@@ -115,26 +120,44 @@ const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>) => Prom
115
120
  mission_decide: dispatchMissionDecide, // 미션 결정 기록(re-ground·defer·boundary…)·3박자 주입·코어
116
121
  };
117
122
 
123
+ /** Worker-side search entry: dispatch must not re-enter its own budget wrapper. */
124
+ export function dispatchRecallUnbudgeted(name: 'memory_recall' | 'self_recall', args: Record<string, unknown>, onPartial: (result: Record<string, unknown>) => void): Promise<unknown> {
125
+ return CORE_TOOL_HANDLERS[name]!(args, onPartial);
126
+ }
127
+
118
128
  /** L2 코어 앱 도구 spec 목록(전 서피스 공용). */
119
- export const CORE_TOOL_SPECS: LLMToolSpec[] = [SCHEDULE_MANAGE_SPEC, SESSION_MANAGE_SPEC, MEMORY_RECALL_SPEC, FACT_CHECK_SPEC, SELF_RECALL_SPEC, AUTOPILOT_MISSION_SPEC, OPS_STATUS_SPEC, SE_BUILD_SPEC, LOGS_QUERY_SPEC, MISSION_DECIDE_SPEC];
129
+ const SELF_RECALL_BUDGET_SPEC: LLMToolSpec = {
130
+ ...SELF_RECALL_SPEC,
131
+ parameters: {
132
+ ...SELF_RECALL_SPEC.parameters,
133
+ properties: {
134
+ ...(SELF_RECALL_SPEC.parameters.properties as Record<string, unknown>),
135
+ timeBudgetMs: { type: 'number', description: '기억 조회 시간 예산(밀리초·기본 60000). 초과 시 찾은 것까지 부분 반환.' },
136
+ },
137
+ },
138
+ };
139
+ export const CORE_TOOL_SPECS: LLMToolSpec[] = [SCHEDULE_MANAGE_SPEC, SESSION_MANAGE_SPEC, MEMORY_RECALL_SPEC, FACT_CHECK_SPEC, SELF_RECALL_BUDGET_SPEC, AUTOPILOT_MISSION_SPEC, OPS_STATUS_SPEC, SE_BUILD_SPEC, LOGS_QUERY_SPEC, MISSION_DECIDE_SPEC];
120
140
 
121
141
  export interface CoreTools {
122
142
  specs: LLMToolSpec[];
123
143
  names: Set<string>;
124
- dispatch: (name: string, args: Record<string, unknown>) => Promise<unknown>;
144
+ dispatch: (name: string, args: Record<string, unknown>, onPartial?: (result: Record<string, unknown>) => void, context?: ToolRuntimeContext) => Promise<unknown>;
125
145
  }
126
146
 
127
147
  /** 서피스 무관 코어 앱 도구 세트. 모든 서피스가 이걸 조립 → 코어 도구 단일 출처 상속.
128
148
  * L1(native)·L3(finance 팩)와 조합해 쓴다. dispatch 는 이름으로 라우팅(미등록=error). */
129
- export function buildCoreTools(): CoreTools {
149
+ export function buildCoreTools(recallSearch?: RecallSearch): CoreTools {
130
150
  const names = new Set(CORE_TOOL_SPECS.map(s => s.name));
131
151
  return {
132
152
  specs: CORE_TOOL_SPECS,
133
153
  names,
134
- dispatch: async (name, args) => {
154
+ dispatch: async (name, args, onPartial, context) => {
135
155
  const h = CORE_TOOL_HANDLERS[name];
136
156
  if (!h) return { error: `unknown core tool: ${name}` };
137
- return h(args);
157
+ if (name === 'memory_recall' || name === 'self_recall') {
158
+ return runRecall(name, args, context ?? { surface: 'skill' }, recallSearch, onPartial);
159
+ }
160
+ return h(args, onPartial);
138
161
  },
139
162
  };
140
163
  }
@@ -6,7 +6,8 @@
6
6
 
7
7
  import * as childProcess from 'node:child_process';
8
8
  import { existsSync, readFileSync, appendFileSync, mkdirSync, unlinkSync } from 'node:fs';
9
- import { dirname, join } from 'node:path';
9
+ import { dirname, join, relative } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
10
11
  import { getElanousConfigDir } from '../elanous-config-dir.js';
11
12
  import { conatusEnv } from './conatus-env.js';
12
13
  import { spillLongContent } from '../storage/content-spill.js';
@@ -17,6 +18,7 @@ import { resolveChannelBotToken } from '../channel-bot-token.js';
17
18
  import { kindRouteTarget } from './telegram-kind-route.js';
18
19
  import { debug } from '../debug/log.js';
19
20
  import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
21
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
20
22
  // ★ origin 되돌림(대표 2026-07-12) — 미션 알림을 발신 채널(메인 Q&A 봇)로 되돌린다. type-only
21
23
  // import 라 런타임 순환 없음(발송 로직은 이 파일에 self-contained). origin 없으면 report 폴백.
22
24
  import type { MissionOrigin } from '../autopilot/mission-origin.js';
@@ -54,7 +56,7 @@ function recentSendBurst(): { recentCount: number; burst: boolean } {
54
56
 
55
57
  /** 발송 1건을 logs.db 에 관측(fail-open) — 발송 시각·mode·kind·밀림 판정. */
56
58
  function logSend(
57
- mode: 'realtime' | 'deferred' | 'quiet-bypass' | 'flush',
59
+ mode: 'realtime' | 'quiet-bypass' | 'flush',
58
60
  kind: string,
59
61
  extra: Record<string, unknown> = {},
60
62
  opts?: { level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'critical' },
@@ -111,15 +113,16 @@ export function userRecentlyActive(now: Date = new Date()): boolean {
111
113
 
112
114
  /** 야간 보류 적재 (jsonl append — 크론 동시 실행에 안전). origin 있으면 함께 적재 —
113
115
  * 아침 flush 가 그 origin(발신 채널)으로 되돌려 발송(없으면 report 묶음). */
114
- function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH): void {
116
+ function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH): boolean {
115
117
  try {
116
118
  mkdirSync(dirname(path), { recursive: true });
117
119
  const rec = { ts: new Date().toISOString(), kind, text, ...(origin ? { origin } : {}) };
118
120
  appendFileSync(path, JSON.stringify(rec) + '\n');
119
121
  console.error(`[outbound] 야간 무음(00:00~06:30 KST) — 보류 적재 (${kind})`);
120
- logSend('deferred', kind, { reason: 'quiet-hours' }); // 밀림 적재 관측
122
+ return true;
121
123
  } catch (e) {
122
124
  console.error(`[outbound] 보류 적재 실패 — 콘솔 출력\n${text}`, e instanceof Error ? e.message : '');
125
+ return false;
123
126
  }
124
127
  }
125
128
 
@@ -139,8 +142,29 @@ function resolveBotToken(botId?: string): string | null {
139
142
  } catch { return null; }
140
143
  }
141
144
 
142
- /** origin(발신 채널)으로 직접 텔레그램 발송. telegram+chatId+토큰 해석 성공 시 true. */
145
+ /** origin(발신 채널)으로 직접 발송. 실패하면 호출자가 report 폴백/재시도한다. */
143
146
  function deliverToOrigin(origin: MissionOrigin, text: string): boolean {
147
+ if (origin.channel === 'discord') {
148
+ if (!origin.channelId) return false;
149
+ try {
150
+ const token = resolveChannelBotToken('discord', getUserConfig())?.token;
151
+ if (!token) return false;
152
+ const channelId = origin.discordThreadId || origin.channelId;
153
+ const out = spillLongContent(text).text;
154
+ for (let start = 0; start < out.length;) {
155
+ let end = Math.min(start + 2000, out.length);
156
+ if (end < out.length && /[\uD800-\uDBFF]/.test(out[end - 1]!)) end--;
157
+ const response = curlPost(
158
+ `https://discord.com/api/v10/channels/${channelId}/messages`,
159
+ JSON.stringify({ content: out.slice(start, end) }),
160
+ [`Authorization: Bot ${token}`, 'Content-Type: application/json'],
161
+ );
162
+ if (typeof response?.id !== 'string' || !response.id) return false;
163
+ start = end;
164
+ }
165
+ return out.length > 0;
166
+ } catch { return false; }
167
+ }
144
168
  if (origin.channel !== 'telegram' || origin.chatId == null) return false;
145
169
  const token = resolveBotToken(origin.botId);
146
170
  if (!token) return false;
@@ -189,11 +213,13 @@ export function flushDeferred(path = DEFERRED_PATH): number {
189
213
  // origin 없는 것 = report 묶음(기존 동작·finance/크론 알림).
190
214
  const noOrigin = items.filter(i => !i.origin);
191
215
  if (noOrigin.length && deliver(fmt(noOrigin), 'report')) delivered += noOrigin.length;
192
- // ★ origin 있는 것 = 발신 채널(botId+chatId)별 묶음으로 되돌려 발송(미션 알림 → 메인 Q&A 봇).
216
+ // origin 있는 것 = 발신 채널·스레드별로 묶어 서로 다른 수신자에게 섞이지 않게 발송.
193
217
  const groups = new Map<string, { origin: MissionOrigin; list: Item[] }>();
194
218
  for (const it of items) {
195
219
  if (!it.origin) continue;
196
- const key = `${it.origin.botId ?? ''}:${it.origin.chatId ?? ''}`;
220
+ const key = it.origin.channel === 'discord'
221
+ ? JSON.stringify(['discord', it.origin.channelId, it.origin.discordThreadId ?? null])
222
+ : JSON.stringify([it.origin.channel, it.origin.botId ?? null, it.origin.chatId ?? null, it.origin.threadId ?? null]);
197
223
  if (!groups.has(key)) groups.set(key, { origin: it.origin, list: [] });
198
224
  groups.get(key)!.list.push(it);
199
225
  }
@@ -228,14 +254,26 @@ export function recordOutbound(text: string, kind: string): void {
228
254
  /** 발송 진입점 — 야간 무음 게이트(00:00~06:30 KST 보류) + 보류분 자동 플러시.
229
255
  * origin(발신 채널) 이 주어지면 그 채널(메인 Q&A 봇 등)로 되돌려 발송 — 무음이면 origin 을
230
256
  * 함께 보류했다가 아침에 그 채널로 flush. origin 없거나 발송 실패 시 report 폴백(기존 동작). */
231
- export function sendOutbound(text: string, kind = 'alert', origin?: MissionOrigin | null): boolean {
257
+ export function sendOutbound(text: string, kind = 'alert', origin?: (MissionOrigin & { surface?: string }) | null): boolean {
258
+ // Prefer the producer's surface; otherwise keep the caller's repository-relative path
259
+ // so two producers named index.ts cannot collapse into one sender.
260
+ const caller = new Error().stack?.split('\n')[2] ?? '';
261
+ const callerPath = caller.match(/(?:\(|\s)(file:\/\/[^\s()]+|\/[^\s()]+\.[cm]?[jt]s):\d+:\d+\)?/)?.[1];
262
+ const repoRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
263
+ const source = origin?.surface || (callerPath
264
+ ? relative(repoRoot, callerPath.startsWith('file://') ? fileURLToPath(callerPath) : callerPath).replace(/\\/g, '/')
265
+ : 'unknown');
266
+ const logOutcome = (event: 'sent' | 'deferred' | 'failed'): void => {
267
+ try { debug.log('outbound.send', event, { kind, source, chars: text.length }); } catch { /* fail-open */ }
268
+ };
232
269
  // ★ 무음 우회(대표 2026-07-14) — 사용자가 최근(기본 30분) genuine 인텐트(타이핑/버튼탭)를 냈으면
233
270
  // 깨어있으므로 야간 무음이어도 즉시 발송(사용자 발원 흐름의 결과물이 아침까지 묶이지 않게).
234
271
  // 우회 시 flushDeferred 로 그간 보류분도 함께 전달(사용자가 지금 볼 수 있음).
235
272
  if (inQuietHours() && !userRecentlyActive()) {
236
- deferOutbound(text, kind, origin);
273
+ const queued = deferOutbound(text, kind, origin);
237
274
  recordOutbound(text, kind); // 보류도 논리적 발송 — 회상 대상(데몬 미경유라 클라 기록)
238
- return true; // 보류 = 수락 (호출측 재시도/에러 루프 방지)
275
+ logOutcome(queued ? 'deferred' : 'failed');
276
+ return true; // 보류 시 수락 — 적재 오류에서도 기존 호출측 재시도 계약 유지
239
277
  }
240
278
  const bypass = inQuietHours();
241
279
  if (bypass) console.error('[outbound] 야간 무음 우회 — 최근 사용자 활동(깨어있음) → 즉시 발송 + 보류분 flush');
@@ -245,14 +283,16 @@ export function sendOutbound(text: string, kind = 'alert', origin?: MissionOrigi
245
283
  const { recentCount, burst } = recentSendBurst();
246
284
  logSend(bypass ? 'quiet-bypass' : 'realtime', kind, { burst, recentCount, ...(burst ? { backlog: true } : {}) });
247
285
  // ★ origin 되돌림(무음 밖) — 발신 채널로 직접 발송. 성공 시 종료, 실패면 report 폴백.
248
- if (origin && origin.channel === 'telegram' && origin.chatId != null && deliverToOrigin(origin, text)) {
286
+ if (origin && deliverToOrigin(origin, text)) {
249
287
  recordOutbound(text, kind);
288
+ logOutcome('sent');
250
289
  return true;
251
290
  }
252
291
  const path = deliver(text, kind);
253
292
  // 원장 기록은 정확히 1회. 데몬 경유(daemon)면 /v1/outbound 핸들러(outbound-report.ts)
254
293
  // 가 기록하므로 클라는 중복 금지 — 직접 폴백(direct·데몬 다운)만 클라가 기록.
255
294
  if (path === 'direct') recordOutbound(text, kind);
295
+ logOutcome(path === false ? 'failed' : 'sent');
256
296
  return path !== false;
257
297
  }
258
298
 
@@ -278,9 +318,31 @@ function logDaemonPath(classification: DaemonPathClass, kind: string, extra: Rec
278
318
  try { console.error(`[outbound] daemon-path ${classification}`); } catch { /* fail-soft */ }
279
319
  }
280
320
 
321
+ /** OB8 — the daemon registers its own in-process sender at startup. Inside the daemon, deliver() must never curl
322
+ * its own `/v1/outbound`: the curl is synchronous, the event loop that would answer it is the one it blocks, and the
323
+ * whole daemon freezes until the curl times out (~25 s · `unreachable`) — the message is lost (CS1 · 10-01). */
324
+ export type InProcessOutbound = (text: string, kind: string) => Promise<boolean>;
325
+ let inProcessOutbound: InProcessOutbound | null = null;
326
+ export function setInProcessOutbound(send: InProcessOutbound | null): void { inProcessOutbound = send; }
327
+
281
328
  /** elanous `/v1/outbound` 우선 → 실패 시 텔레그램 직접. 성공 경로 반환(원장 중복방지용). */
282
329
  export function deliver(text: string, kind = 'alert'): 'daemon' | 'direct' | false {
330
+ // 0) inside the daemon — route in-process, asynchronously; the caller's synchronous answer is «accepted».
331
+ if (inProcessOutbound && process.env.SEND_VIA_ELANOUS !== '0') {
332
+ const send = inProcessOutbound;
333
+ void send(text, kind)
334
+ .then((ok) => {
335
+ logDaemonPath(ok ? 'ok' : 'rejected', kind, { inProcess: true });
336
+ if (!ok && sendTelegramDirect(text, kind)) recordOutbound(text, kind);
337
+ })
338
+ .catch((error: unknown) => {
339
+ logDaemonPath('rejected', kind, { inProcess: true, error: error instanceof Error ? error.message : String(error) });
340
+ if (sendTelegramDirect(text, kind)) recordOutbound(text, kind);
341
+ });
342
+ return 'daemon';
343
+ }
283
344
  // 1) elanous 단일 발송 지점(/v1/outbound) — 데몬이 팬아웃 + 원장 기록.
345
+ let daemonPath: DaemonPathClass | 'not-found' | 'disabled' = 'disabled';
284
346
  if (process.env.SEND_VIA_ELANOUS !== '0') {
285
347
  let token = '';
286
348
  try { if (existsSync(ACP_TOKEN_PATH)) token = readFileSync(ACP_TOKEN_PATH, 'utf-8').trim(); } catch { /* no token */ }
@@ -291,12 +353,32 @@ export function deliver(text: string, kind = 'alert'): 'daemon' | 'direct' | fal
291
353
  : null;
292
354
  const classification = classifyDaemonResponse(j);
293
355
  if (classification === 'ok') return 'daemon';
356
+ daemonPath = nexus ? classification : 'not-found';
294
357
  const extra: Record<string, unknown> = { hasToken: token.length > 0 };
295
358
  if (j && typeof j === 'object' && 'error' in (j as object)) extra.error = (j as { error?: unknown }).error;
296
359
  logDaemonPath(classification, kind, extra);
297
360
  }
298
361
  // 2) fallback: 텔레그램 sendMessage 직접(3900자 분할) — 데몬 미경유라 클라가 원장 기록.
299
- return sendTelegramDirect(text, kind) ? 'direct' : false;
362
+ if (sendTelegramDirect(text, kind)) return 'direct';
363
+ reportUndeliverable(kind, daemonPath);
364
+ return false;
365
+ }
366
+
367
+ /** OB8b — both paths failed: say so loudly, with the universe this process resolved, instead of a silent false.
368
+ * The usual cause (10-01 · MK): an ad-hoc `bun -e` or a script from a source tree resolves a cwd-derived test
369
+ * universe, so it finds neither the production daemon nor the production bot token. */
370
+ function reportUndeliverable(kind: string, daemonPath: DaemonPathClass | 'not-found' | 'disabled'): void {
371
+ let root = '?';
372
+ let universe: 'prod' | 'test' | '?' = '?';
373
+ try {
374
+ root = effectiveInstanceRoot();
375
+ universe = root === prodInstanceRoot() ? 'prod' : 'test';
376
+ } catch { /* the report still goes out */ }
377
+ try { debug.log('outbound.send', 'undeliverable', { kind, daemonPath, universe, root }); } catch { /* fail-soft */ }
378
+ const hint = universe === 'prod'
379
+ ? '운영 데몬이 떠 있는지 확인: elanous nexus show'
380
+ : '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous ELANOUS_CONFIG_DIR=~/.elanous 를 준다';
381
+ try { console.error(`[outbound] ⛔ 못 보냄(${kind}) — 데몬 ${daemonPath} · 직접 발송도 실패(토큰 없음 또는 전송 실패) · 이 프로세스의 우주 ${universe} (${root}) · ${hint}`); } catch { /* fail-soft */ }
300
382
  }
301
383
 
302
384
  /** 텔레그램 raw 발송(토큰·chatId 명시) — spill + 3900자 분할(줄 경계). thread 지원. */
@@ -79,7 +79,7 @@ export function explainFailure(group: RepeatedFailureGroup): string {
79
79
  // the first live tick saw it as `webterm.tabs` (list errors re-raise the socket close) and sent the generic line.
80
80
  if (group.area.startsWith('webterm.') && /^socket closed: #: auth_failed$/i.test(group.reason)) {
81
81
  // Same words as the PWA banners (🅞 #21591·#21602 · 🅕 #21599) so the alert and the screen say one thing.
82
- return '이 기기(브라우저)에 소유자 토큰이 없거나 틀리거나 만료돼 데몬이 연결을 거절한다 — 이미 연결된 기기의 설정 › Connect token 에서 토큰을 만들어, 이 기기의 설정 › Daemon › Bearer token 칸(/app/settings/#bearer-token)에 붙인다';
82
+ return '이 기기(브라우저)에 소유자 토큰이 없거나 틀리거나 만료돼 데몬이 연결을 거절한다 — 이미 연결된 기기의 설정 › 연결 토큰 만들기 에서 토큰을 만들어, 이 기기의 설정 › 데몬 연결 › 연결 토큰 칸(/app/settings/#bearer-token)에 붙인다';
83
83
  }
84
84
  const span = Date.parse(group.lastTs) - Date.parse(group.firstTs);
85
85
  const minutes = Number.isFinite(span) ? Math.max(1, Math.ceil(span / 60_000)) : 10;