elanous 0.2.5 → 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 (479) hide show
  1. package/.env.example +10 -7
  2. package/README.md +9 -4
  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/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
  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/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  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/{8617.465ca22a319c95b3.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/{8918-482b30ce2a8ebf4c.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/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
  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/{f6f3850d-7f324c6566002113.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-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
  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-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
  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-2bf59c720fe9542c.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/icons/elanous-16.png +0 -0
  123. package/apps/pwa/out/icons/elanous-180.png +0 -0
  124. package/apps/pwa/out/icons/elanous-192.png +0 -0
  125. package/apps/pwa/out/icons/elanous-32.png +0 -0
  126. package/apps/pwa/out/icons/elanous-512.png +0 -0
  127. package/apps/pwa/out/index.html +1 -1
  128. package/apps/pwa/out/index.txt +21 -21
  129. package/apps/pwa/out/intake/index.html +1 -1
  130. package/apps/pwa/out/intake/index.txt +22 -22
  131. package/apps/pwa/out/live/index.html +1 -1
  132. package/apps/pwa/out/live/index.txt +22 -22
  133. package/apps/pwa/out/market/index.html +1 -1
  134. package/apps/pwa/out/market/index.txt +21 -21
  135. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  136. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  137. package/apps/pwa/out/morning/index.html +1 -1
  138. package/apps/pwa/out/morning/index.txt +22 -22
  139. package/apps/pwa/out/observatory/index.html +1 -1
  140. package/apps/pwa/out/observatory/index.txt +22 -22
  141. package/apps/pwa/out/reflection/index.html +1 -1
  142. package/apps/pwa/out/reflection/index.txt +22 -22
  143. package/apps/pwa/out/scheduler/index.html +1 -1
  144. package/apps/pwa/out/scheduler/index.txt +22 -22
  145. package/apps/pwa/out/sessions/index.html +1 -1
  146. package/apps/pwa/out/sessions/index.txt +21 -21
  147. package/apps/pwa/out/settings/devices/index.html +1 -1
  148. package/apps/pwa/out/settings/devices/index.txt +22 -22
  149. package/apps/pwa/out/settings/index.html +1 -1
  150. package/apps/pwa/out/settings/index.txt +22 -22
  151. package/apps/pwa/out/setup/done/index.html +1 -1
  152. package/apps/pwa/out/setup/done/index.txt +22 -22
  153. package/apps/pwa/out/setup/index.html +1 -1
  154. package/apps/pwa/out/setup/index.txt +22 -22
  155. package/apps/pwa/out/share/index.html +1 -1
  156. package/apps/pwa/out/share/index.txt +22 -22
  157. package/apps/pwa/out/showroom/index.html +1 -1
  158. package/apps/pwa/out/showroom/index.txt +21 -21
  159. package/apps/pwa/out/tasks/index.html +1 -1
  160. package/apps/pwa/out/tasks/index.txt +22 -22
  161. package/apps/pwa/out/term/index.html +1 -1
  162. package/apps/pwa/out/term/index.txt +22 -22
  163. package/apps/pwa/out/trace/index.html +1 -1
  164. package/apps/pwa/out/trace/index.txt +22 -22
  165. package/apps/pwa/out/vault/index.html +1 -1
  166. package/apps/pwa/out/vault/index.txt +22 -22
  167. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  168. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  169. package/apps/pwa/out/workflows/index.html +1 -1
  170. package/apps/pwa/out/workflows/index.txt +22 -22
  171. package/apps/pwa/out/workspace/index.html +1 -1
  172. package/apps/pwa/out/workspace/index.txt +22 -22
  173. package/apps/pwa/out/worktrees/index.html +1 -1
  174. package/apps/pwa/out/worktrees/index.txt +22 -22
  175. package/bin/elanous.cjs +107 -0
  176. package/graphs/field/field-feed.yaml +24 -0
  177. package/graphs/field/recipes.yaml +12 -0
  178. package/graphs/landing/landing-heal.yaml +3 -1
  179. package/graphs/landing/recipes.yaml +3 -0
  180. package/graphs/release/recipes.yaml +13 -0
  181. package/graphs/release/release-loop.yaml +13 -4
  182. package/package.json +5 -5
  183. package/plugins/card-followup/README.md +42 -0
  184. package/plugins/card-followup/examples/card-sample.json +7 -0
  185. package/plugins/card-followup/graphs/card-followup.yaml +18 -0
  186. package/plugins/card-followup/graphs/recipes.yaml +16 -0
  187. package/plugins/card-followup/graphs/run-step.ts +276 -0
  188. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  189. package/plugins/card-followup/plugin.json +25 -0
  190. package/plugins/doc-draft/README.md +13 -0
  191. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  192. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  193. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  194. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  195. package/plugins/doc-draft/plugin.json +24 -0
  196. package/plugins/geo-check/README.md +15 -0
  197. package/plugins/geo-check/examples/brand-sample.json +8 -0
  198. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  199. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  200. package/plugins/geo-check/graphs/run-step.ts +198 -0
  201. package/plugins/geo-check/plugin.json +24 -0
  202. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  203. package/plugins/job-coach/README.md +31 -1
  204. package/plugins/job-coach/graphs/run-step.ts +2 -2
  205. package/plugins/job-coach/plugin.json +1 -1
  206. package/plugins/job-coach/plugin.ts +1 -1
  207. package/scripts/affinity/endcard.sh +3 -2
  208. package/scripts/affinity/reframe.sh +3 -1
  209. package/scripts/ci-public-export-leak-gate.ts +48 -0
  210. package/scripts/codex-quota-alert.ts +122 -258
  211. package/scripts/coord-tracks.json +4 -4
  212. package/scripts/daemon-port-baseline.txt +1 -2
  213. package/scripts/field-feed/deliver-node.ts +52 -0
  214. package/scripts/field-feed/feed-node.ts +67 -0
  215. package/scripts/field-feed/lib.ts +162 -0
  216. package/scripts/field-feed/reel-node.ts +24 -0
  217. package/scripts/field-feed/start.ts +62 -0
  218. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  219. package/scripts/install.sh +92 -13
  220. package/scripts/landing-heal/clean-checkout.ts +114 -0
  221. package/scripts/landing-heal/run-mission.ts +38 -3
  222. package/scripts/lib/android-test-device.ts +32 -0
  223. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  224. package/scripts/public-export.ts +8 -4
  225. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  226. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  227. package/scripts/release-loop/env-known-failures.ts +31 -0
  228. package/scripts/release-loop/gate-diff.ts +29 -0
  229. package/scripts/release-loop/gate-node.ts +124 -29
  230. package/scripts/release-loop/known-issues-node.ts +159 -0
  231. package/scripts/release-loop/npm-publish-node.ts +118 -0
  232. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  233. package/scripts/release-loop/release-readiness.ts +58 -0
  234. package/scripts/release-loop/shard-plan.ts +47 -0
  235. package/scripts/release-loop/unattended-release.ts +93 -0
  236. package/scripts/ux/measure-first-text.ts +102 -0
  237. package/scripts/ux/record-page.ts +4 -1
  238. package/scripts/vflow-collect.ts +20 -7
  239. package/scripts/vflow-search.ts +73 -0
  240. package/scripts/video-free-line-test-walker.ts +36 -0
  241. package/scripts/video-pipeline.ts +2 -2
  242. package/skills/explainer-video/SKILL.md +24 -3
  243. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
  244. package/skills/explainer-video/engine/build.mjs +2 -2
  245. package/skills/explainer-video/engine/reel.mjs +235 -0
  246. package/skills/explainer-video/engine/reel.sh +9 -0
  247. package/skills/explainer-video/engine/vo.ts +58 -16
  248. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  249. package/skills/grill-me/SKILL.md +1 -1
  250. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  251. package/src/a2a/server.ts +2 -0
  252. package/src/acp/elanous-aware-peers.ts +30 -0
  253. package/src/acp/server.ts +91 -15
  254. package/src/agent/monad-agent-turn.ts +25 -6
  255. package/src/agent/shared-app-tools.ts +5 -3
  256. package/src/agent-substrate/review-intent.ts +6 -2
  257. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  258. package/src/auth/github-app-token.ts +4 -1
  259. package/src/autopilot/mission-origin.ts +20 -4
  260. package/src/autopilot/mission-phase-track.ts +11 -1
  261. package/src/autopilot/track-agent-shadow.ts +151 -0
  262. package/src/autopilot/track-agent.ts +26 -6
  263. package/src/autopilot/track-courier.ts +2 -2
  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/chat/chat-cli.ts +4 -1
  271. package/src/chat/short-question.ts +40 -0
  272. package/src/cli/agent-cli.ts +24 -15
  273. package/src/cli/ask-bare.ts +42 -0
  274. package/src/cli/cli-user-error.ts +25 -0
  275. package/src/cli/codex-credit-plan-view.ts +25 -0
  276. package/src/cli/dependence-gauge.ts +237 -0
  277. package/src/cli/doctor-cli.ts +5 -0
  278. package/src/cli/doctor-modes.ts +48 -0
  279. package/src/cli/intake-cli.ts +12 -2
  280. package/src/cli/intake-source-cli.ts +58 -0
  281. package/src/cli/machine-cli.ts +23 -0
  282. package/src/cli/msg-cli.ts +137 -0
  283. package/src/cli/nexus-ios-bind.ts +25 -8
  284. package/src/cli/phone-link.ts +117 -0
  285. package/src/cli/plugin-cli.ts +52 -0
  286. package/src/cli/pr-cli.ts +36 -0
  287. package/src/cli/private-ledger-commands.ts +31 -0
  288. package/src/cli/provider-cli.ts +5 -0
  289. package/src/cli/release-cli.ts +56 -12
  290. package/src/cli/release-run-if-ready.ts +48 -0
  291. package/src/cli/setup-cli.ts +27 -6
  292. package/src/cli/setup-recommend.ts +8 -0
  293. package/src/cli/usage-cli.ts +11 -1
  294. package/src/codex/setup.ts +27 -8
  295. package/src/dashboard/index.ts +1 -1
  296. package/src/discord.ts +56 -6
  297. package/src/domains/core-tools.ts +37 -14
  298. package/src/domains/outbound-alert.ts +55 -16
  299. package/src/exec-requests/planner.ts +140 -0
  300. package/src/exec-requests/runner.ts +338 -0
  301. package/src/exec-requests/store.ts +76 -0
  302. package/src/field/field-media.ts +250 -0
  303. package/src/field/field-reel.ts +196 -0
  304. package/src/grok/credential.ts +7 -5
  305. package/src/harness/aside-run-status.ts +33 -0
  306. package/src/harness/browser-verify.ts +48 -17
  307. package/src/harness/harness-cli-command.ts +25 -3
  308. package/src/harness/image-load-state.ts +21 -0
  309. package/src/harness/process-garbage.ts +45 -0
  310. package/src/index.ts +104 -38
  311. package/src/instance/leader.ts +13 -1
  312. package/src/intake-plane/collect-github.ts +5 -3
  313. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  314. package/src/intake-plane/discord-seat-work.ts +56 -0
  315. package/src/intake-plane/intake-sources.ts +202 -0
  316. package/src/intake-plane/items.ts +28 -12
  317. package/src/intake-plane/seat-doc-route.ts +207 -0
  318. package/src/intake-plane/submit-intake-work.ts +9 -1
  319. package/src/intake-plane/telegram-seat-work.ts +72 -0
  320. package/src/llm/model-defaults.ts +24 -1
  321. package/src/llm.ts +5 -4
  322. package/src/machines/machine-ledger.ts +127 -0
  323. package/src/market/pack-internal-refs.ts +28 -0
  324. package/src/market/publish.ts +31 -4
  325. package/src/maturity/feature-maturity.ts +65 -0
  326. package/src/model-tier/turn-tier.ts +24 -0
  327. package/src/msg/msg-store.ts +149 -0
  328. package/src/native-tool-catalog.ts +2 -1
  329. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  330. package/src/nexus/api/consult-requests.ts +117 -0
  331. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  332. package/src/nexus/api/exec-requests.ts +51 -0
  333. package/src/nexus/api/field-uploads.ts +180 -0
  334. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  335. package/src/nexus/api/graph-approvals.ts +120 -4
  336. package/src/nexus/api/harness-api.ts +219 -5
  337. package/src/nexus/api/http-server.ts +68 -5
  338. package/src/nexus/api/seat-requests.ts +254 -0
  339. package/src/nexus/api/trace-run-scan.ts +37 -0
  340. package/src/nexus/api/trace.ts +19 -3
  341. package/src/nexus/index.ts +3 -0
  342. package/src/nexus/setup-status.ts +4 -5
  343. package/src/notify/llm-budget-alert.ts +125 -0
  344. package/src/oauth/codex-account-rotation.ts +26 -4
  345. package/src/oauth/codex-account-store.ts +20 -2
  346. package/src/oauth/codex-browser-login.ts +173 -0
  347. package/src/oauth/codex.ts +4 -1
  348. package/src/oauth/fallback-chain.ts +5 -5
  349. package/src/oauth/store.ts +42 -5
  350. package/src/onboarding/continue-here.ts +29 -0
  351. package/src/onboarding/entry-hints.ts +8 -0
  352. package/src/onboarding/full-screen-io.ts +35 -3
  353. package/src/onboarding/help/skills.md +1 -1
  354. package/src/onboarding/non-interactive.ts +1 -0
  355. package/src/onboarding/subscription-hint.ts +27 -0
  356. package/src/onboarding/web-first.ts +83 -0
  357. package/src/onboarding.ts +230 -23
  358. package/src/plugins/maker/node-install.ts +92 -0
  359. package/src/plugins/maker/node-maker.ts +139 -0
  360. package/src/plugins/maker/plugin-maker.ts +220 -0
  361. package/src/policy/llm-policy.ts +268 -0
  362. package/src/prompt-library/session-guidance.ts +7 -1
  363. package/src/prompt-library/universal-preamble.ts +2 -2
  364. package/src/relay/channel.ts +184 -0
  365. package/src/relay/mux.ts +182 -0
  366. package/src/relay/relay-cli.ts +20 -0
  367. package/src/relay/relay-server.ts +220 -0
  368. package/src/relay/test-vectors.json +29 -0
  369. package/src/release-loop/checklist.ts +36 -9
  370. package/src/seat-address/seat-address.ts +34 -0
  371. package/src/self-dev/decompose.ts +1 -0
  372. package/src/self-dev/dev-pipeline.ts +6 -3
  373. package/src/self-dev/run-record-mirror.ts +44 -0
  374. package/src/self-dev/run-store.ts +12 -3
  375. package/src/self-implement/harness-policy-gates.ts +4 -1
  376. package/src/self-implement/report-origin.ts +12 -1
  377. package/src/self-implement/run-ledger-compact.ts +1 -1
  378. package/src/session/chat.ts +17 -5
  379. package/src/session/index.ts +23 -10
  380. package/src/skills/shared-agent-skill-roots.ts +25 -0
  381. package/src/steward/failure-streak.ts +78 -0
  382. package/src/steward/steward-cards.ts +24 -0
  383. package/src/steward/triage-budget.ts +127 -0
  384. package/src/steward/triage-plan.ts +75 -0
  385. package/src/steward/triage.ts +244 -42
  386. package/src/task-cards/card-store.ts +1 -1
  387. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  388. package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
  389. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  390. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  391. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  392. package/src/telegram.ts +145 -2
  393. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  394. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  395. package/src/user-config.ts +84 -4
  396. package/src/version/packed-revision.json +1 -1
  397. package/src/video-pipeline/recipes/character.ts +42 -5
  398. package/src/video-pipeline/recipes/explainer.ts +213 -0
  399. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  400. package/src/video-pipeline/recipes/free-line.ts +16 -3
  401. package/src/video-pipeline/recipes/remote.ts +7 -1
  402. package/src/video-pipeline/vflow-reference.ts +109 -0
  403. package/src/video-pipeline/walk-line.ts +3 -2
  404. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
  411. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
  419. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
  477. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  478. package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
  479. /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -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 빌드(빌드 산출은 공개본에 안 실린다 → 빠뜨리면 «웹 화면 없는 판»). 절차를 명령으로 굳힌다.
@@ -22,7 +23,11 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
22
23
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
23
24
  import { envLiteral } from '../platform/env-literal.js';
24
25
  import { userConfigPath } from '../user-config.js';
25
- import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus } from '../release-loop/checklist.js';
26
+ import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition } from '../release-loop/checklist.js';
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';
26
31
 
27
32
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
28
33
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -466,11 +471,11 @@ function checklistVersion(input: string | undefined, codenames: Record<string, s
466
471
  return resolved;
467
472
  }
468
473
 
469
- function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): void {
474
+ async function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): Promise<void> {
470
475
  const data = listChecklist(v);
471
476
  const summary = summarizeChecklist(data);
472
477
  const result = { ...data, codename: codenames[v] ?? '', ...summary };
473
- if (json) { console.log(JSON.stringify(result)); return; }
478
+ if (json) { await writeStdoutJson(`${JSON.stringify(result)}\n`); return; }
474
479
  console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
475
480
  if (mode === 'list') {
476
481
  for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
@@ -481,7 +486,7 @@ function checklistOutput(v: string, codenames: Record<string, string>, mode: 'li
481
486
  }
482
487
  }
483
488
 
484
- export function registerReleaseCommands(program: Command): void {
489
+ export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}): void {
485
490
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
486
491
  const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
487
492
  .option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
@@ -492,26 +497,28 @@ export function registerReleaseCommands(program: Command): void {
492
497
  return { version: checklistVersion(opts.version, codenames), codenames, json: opts.json };
493
498
  };
494
499
  const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON');
495
- withContext(checklist.command('list').description('모든 칸')).action((_opts: unknown, cmd: Command) => {
500
+ withContext(checklist.command('list').description('모든 칸')).action(async (_opts: unknown, cmd: Command) => {
496
501
  const { version, codenames, json } = context(cmd);
497
- checklistOutput(version, codenames, 'list', json);
502
+ await checklistOutput(version, codenames, 'list', json);
498
503
  });
499
- withContext(checklist.command('status').description('상태 요약')).action((_opts: unknown, cmd: Command) => {
504
+ withContext(checklist.command('status').description('상태 요약')).action(async (_opts: unknown, cmd: Command) => {
500
505
  const { version, codenames, json } = context(cmd);
501
- checklistOutput(version, codenames, 'status', json);
506
+ await checklistOutput(version, codenames, 'status', json);
502
507
  });
503
508
  withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').action((id: string, title: string, opts: { owner?: string }, cmd: Command) => {
504
509
  const { version, json } = context(cmd);
505
510
  const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}) });
506
511
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
507
512
  });
508
- withContext(checklist.command('set <id>').description('칸 상태·근거·담당 갱신'))
513
+ withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
509
514
  .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
510
- .action((id: string, opts: { status?: string; evidence?: string; owner?: string }, cmd: Command) => {
515
+ .option('--disposition <disposition>', 'move|known-issue|block')
516
+ .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string }, cmd: Command) => {
511
517
  const { version, json } = context(cmd);
512
518
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new Error(`잘못된 상태: ${opts.status}`);
513
- if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined) throw new Error('갱신할 칸을 지정하라');
514
- const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}) }, process.env.ELANOUS_TRACK || 'cli');
519
+ if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new Error(`잘못된 처분: ${opts.disposition}`);
520
+ if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined) throw new Error('갱신할 칸을 지정하라');
521
+ const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}) }, process.env.ELANOUS_TRACK || 'cli');
515
522
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 갱신`);
516
523
  });
517
524
  withContext(checklist.command('rm <id>').description('칸 삭제')).action((id: string, _opts: unknown, cmd: Command) => {
@@ -584,4 +591,41 @@ export function registerReleaseCommands(program: Command): void {
584
591
  const { draftReleaseNotes, readLandedCommits, renderReleaseNotes } = await import('../../scripts/release-notes.js');
585
592
  console.log(renderReleaseNotes(draftReleaseNotes(readLandedCommits(o.from, o.to), o.from, o.to)));
586
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
+ });
587
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;
@@ -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
  }