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/acp/server.ts CHANGED
@@ -26,6 +26,7 @@
26
26
  //
27
27
  // Related: src/acp/client.ts (elanous-as-client path — pre-MT5 work).
28
28
 
29
+ import { ACP_LOGIN_METHOD_ID, acpAuthMethods } from './auth-methods.js';
29
30
  import { AgentSideConnection, ndJsonStream, RequestError } from '@agentclientprotocol/sdk';
30
31
  import type {
31
32
  InitializeRequest, InitializeResponse,
@@ -66,6 +67,7 @@ import {
66
67
  type ElanousTermPayload,
67
68
  } from './elanous-extensions.js';
68
69
  import { createMissionTurnEmitter } from './mission-turn-emit.js';
70
+ import { ElanousAwarePeers } from './elanous-aware-peers.js';
69
71
  import { enqueuePendingUserInput } from '../session/pending-input.js';
70
72
  import {
71
73
  ELANOUS_ASK_CANCEL_METHOD,
@@ -879,6 +881,10 @@ let activeAcpBroadcaster:
879
881
  | ((sessionId: string, update: unknown) => Promise<{ delivered: number }>)
880
882
  | null = null;
881
883
 
884
+ let activeAcpFeedbackBroadcaster:
885
+ | ((sessionId: string, env: FeedbackEnvelope) => Promise<{ delivered: number }>)
886
+ | null = null;
887
+
882
888
  // P0b(DESIGN-cross-surface-autonomy-membrane §10) — ACP ask pusher(elanous/ask → iPhone/PWA 시트)를 모듈-레벨로
883
889
  // 노출. daemon-runtime 이 코어 데몬 턴(SelfImplement dispatch)에 SurfaceUx confirm/question 채널을 주입할 때
884
890
  // createAcp*Channel(sessionId, getActiveAcpAskPusher()) 로 사용. sessionPeers 로 라우팅(연결 무관)·fail-soft(null=drop).
@@ -894,8 +900,8 @@ export function getActiveAcpBroadcaster():
894
900
  }
895
901
 
896
902
  /** PLAN-ios-rich-dev-feedback-hydrate · M1-S (2026-05-13) — typed
897
- * adapter on top of `activeAcpBroadcaster` that fans out a
898
- * `FeedbackEnvelope` as a `elanous/feedback/emit` envelope packaged
903
+ * adapter that fans out a `FeedbackEnvelope` only to peers advertising
904
+ * `_meta.elanous`, as a `elanous/feedback/emit` envelope packaged
899
905
  * inside an `agent_thought_chunk` sessionUpdate.
900
906
  *
901
907
  * Why `agent_thought_chunk` instead of a native `sessionUpdate:
@@ -912,15 +918,7 @@ export function getActiveAcpBroadcaster():
912
918
  export function getActiveAcpFeedbackBroadcaster():
913
919
  | ((sessionId: string, env: FeedbackEnvelope) => Promise<{ delivered: number }>)
914
920
  | null {
915
- const inner = activeAcpBroadcaster;
916
- if (!inner) return null;
917
- return (sessionId, env) => {
918
- const text = formatElanousFeedbackEnvelope({ method: 'emit', payload: env });
919
- return inner(sessionId, {
920
- sessionUpdate: 'agent_thought_chunk',
921
- content: { type: 'text', text },
922
- });
923
- };
921
+ return activeAcpFeedbackBroadcaster;
924
922
  }
925
923
 
926
924
  /** PLAN-ios-rich-dev-feedback-hydrate M4 (HUD) — fan-out across ALL
@@ -989,6 +987,9 @@ interface AcpServerContext {
989
987
  * fan-out walks the set so multi-surface clients (e.g. web PWA
990
988
  * + TUI on the same id) all see the same stream. */
991
989
  sessionPeers: Map<string, Set<SessionPeer>>;
990
+ awarePeers: ElanousAwarePeers<SessionPeer>;
991
+ /** One skip diagnostic per session, cleared when its last peer leaves. */
992
+ plainEnvelopeLogged: Set<string>;
992
993
  dualRole: DualRoleManager;
993
994
  /** Mints the unique suffix of a new session id. Injected so tests can
994
995
  * pin it deterministically — production wires `mintAcpSessionToken`. */
@@ -1029,6 +1030,21 @@ interface SessionPeer {
1029
1030
  peerId: string | null;
1030
1031
  }
1031
1032
 
1033
+ function isFeedbackTextUpdate(update: unknown): boolean {
1034
+ if (!update || typeof update !== 'object') return false;
1035
+ const u = update as { sessionUpdate?: unknown; content?: { type?: unknown; text?: unknown } };
1036
+ return u.sessionUpdate === 'agent_thought_chunk'
1037
+ && u.content?.type === 'text'
1038
+ && typeof u.content.text === 'string'
1039
+ && u.content.text.startsWith('[elanous/feedback/emit] ');
1040
+ }
1041
+
1042
+ function logSkippedPlainEnvelope(ctx: AcpServerContext, sessionId: string, kind: string): void {
1043
+ if (ctx.plainEnvelopeLogged.has(sessionId)) return;
1044
+ ctx.plainEnvelopeLogged.add(sessionId);
1045
+ debug.log('acp.envelope', 'skipped-plain-client', { sessionId, kind });
1046
+ }
1047
+
1032
1048
  /** Wire a single `AgentSideConnection` onto the provided ndJsonStream.
1033
1049
  * Owns all per-connection state (clientUiCaps, boundConnection ref,
1034
1050
  * per-peer `AcpServerHandle`) so stdio and transport paths diverge
@@ -1038,7 +1054,7 @@ function wireAcpConnection(
1038
1054
  stream: ReturnType<typeof ndJsonStream>,
1039
1055
  ctx: AcpServerContext,
1040
1056
  ): AgentSideConnection {
1041
- const { sessions, sessionPeers, dualRole, opts } = ctx;
1057
+ const { sessions, sessionPeers, awarePeers, plainEnvelopeLogged, dualRole, opts } = ctx;
1042
1058
  // 2026-05-13 (M2 of AskUserQuestion cross-surface) — bound connection
1043
1059
  // widened to expose `extMethod` + `extNotification` (SDK's elanous
1044
1060
  // extension escape hatch). pushAskRequest / pushAskCancel route through
@@ -1132,6 +1148,10 @@ function wireAcpConnection(
1132
1148
  }
1133
1149
  return { delivered: 0 };
1134
1150
  }
1151
+ if (isFeedbackTextUpdate(update) && !awarePeers.isElanousAware(sessionId)) {
1152
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
1153
+ return { delivered: 0 };
1154
+ }
1135
1155
  let delivered = 0;
1136
1156
  let dedupedByPeerId = 0;
1137
1157
  const tasks: Promise<unknown>[] = [];
@@ -1141,6 +1161,10 @@ function wireAcpConnection(
1141
1161
  // 은 모두 distinct peer 으로 취급해 send (degraded compat).
1142
1162
  const seenPeerIds = new Set<string>();
1143
1163
  for (const p of peers) {
1164
+ if (isFeedbackTextUpdate(update) && !awarePeers.isPeerAware(p)) {
1165
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
1166
+ continue;
1167
+ }
1144
1168
  if (opts2?.uiGate && !p.getUiCaps()[opts2.uiGate]) continue;
1145
1169
  if (opts2?.termGate && !p.getTermCaps()[opts2.termGate]) continue;
1146
1170
  if (p.peerId !== null) {
@@ -1380,6 +1404,7 @@ function wireAcpConnection(
1380
1404
  clientUiCaps = parsed.ui;
1381
1405
  clientTermCaps = parsed.term;
1382
1406
  clientAskCaps = parsed.ask;
1407
+ awarePeers.markPeer(peer, req.clientCapabilities);
1383
1408
  debug.log('acp.session', 'initialize', {
1384
1409
  protocolVersion: req.protocolVersion,
1385
1410
  clientName: req.clientInfo?.name ?? 'absent',
@@ -1391,14 +1416,13 @@ function wireAcpConnection(
1391
1416
  agentCapabilities: opts.agentBrand !== undefined && opts.agentModel !== undefined
1392
1417
  ? buildAgentDeclaration({ brand: opts.agentBrand, model: opts.agentModel })
1393
1418
  : buildAgentDeclaration(),
1394
- authMethods: [],
1419
+ authMethods: acpAuthMethods(req.clientCapabilities),
1395
1420
  };
1396
1421
  },
1397
1422
 
1398
- async authenticate(_req: AuthenticateRequest): Promise<AuthenticateResponse> {
1399
- // No auth flow — we advertise authMethods: [] so the client
1400
- // shouldn't call this, but the Agent interface requires the
1401
- // method to be present.
1423
+ async authenticate(req: AuthenticateRequest): Promise<AuthenticateResponse> {
1424
+ // EN10a — sign-in happens in a terminal (`--acp-server --login`); this only acknowledges the method so the client retries.
1425
+ debug.log('acp.session', 'authenticate', { methodId: req.methodId === ACP_LOGIN_METHOD_ID ? req.methodId : 'other' });
1402
1426
  return {};
1403
1427
  },
1404
1428
 
@@ -3316,7 +3340,10 @@ function wireAcpConnection(
3316
3340
  if (!set) continue;
3317
3341
  set.delete(peer);
3318
3342
  const remaining = set.size;
3319
- if (remaining === 0) sessionPeers.delete(sid);
3343
+ if (remaining === 0) {
3344
+ sessionPeers.delete(sid);
3345
+ plainEnvelopeLogged.delete(sid);
3346
+ }
3320
3347
  if (debug.enabled) {
3321
3348
  debug.log('acp.peer.unregister', sid, { remainingPeers: remaining });
3322
3349
  }
@@ -3451,10 +3478,14 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3451
3478
  return true;
3452
3479
  });
3453
3480
  const sessionPeers = new Map<string, Set<SessionPeer>>();
3481
+ const awarePeers = new ElanousAwarePeers(sessionPeers);
3482
+ const plainEnvelopeLogged = new Set<string>();
3454
3483
  const dualRole = opts.dualRoleManager ?? globalDualRoleManager();
3455
3484
  const ctx: AcpServerContext = {
3456
3485
  sessions,
3457
3486
  sessionPeers,
3487
+ awarePeers,
3488
+ plainEnvelopeLogged,
3458
3489
  dualRole,
3459
3490
  nextSessionToken: mintAcpSessionToken,
3460
3491
  agentName: opts.agentName ?? 'elanous-agent',
@@ -3478,9 +3509,17 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3478
3509
  }
3479
3510
  return { delivered: 0 };
3480
3511
  }
3512
+ if (isFeedbackTextUpdate(update) && !awarePeers.isElanousAware(sessionId)) {
3513
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3514
+ return { delivered: 0 };
3515
+ }
3481
3516
  const tasks: Promise<unknown>[] = [];
3482
3517
  let delivered = 0;
3483
3518
  for (const p of peers) {
3519
+ if (isFeedbackTextUpdate(update) && !awarePeers.isPeerAware(p)) {
3520
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3521
+ continue;
3522
+ }
3484
3523
  delivered += 1;
3485
3524
  tasks.push(p.sessionUpdate({ sessionId, update }));
3486
3525
  }
@@ -3496,19 +3535,52 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3496
3535
  await Promise.allSettled(tasks);
3497
3536
  return { delivered };
3498
3537
  };
3538
+ activeAcpFeedbackBroadcaster = async (sessionId, env) => {
3539
+ const text = formatElanousFeedbackEnvelope({ method: 'emit', payload: env });
3540
+ const peers = sessionPeers.get(sessionId);
3541
+ if (!peers || peers.size === 0) return { delivered: 0 };
3542
+ if (!awarePeers.isElanousAware(sessionId)) {
3543
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3544
+ return { delivered: 0 };
3545
+ }
3546
+ let delivered = 0;
3547
+ const tasks: Promise<unknown>[] = [];
3548
+ for (const peer of peers) {
3549
+ if (!awarePeers.isPeerAware(peer)) {
3550
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3551
+ continue;
3552
+ }
3553
+ delivered += 1;
3554
+ tasks.push(peer.sessionUpdate({
3555
+ sessionId,
3556
+ update: { sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text } },
3557
+ }));
3558
+ }
3559
+ await Promise.allSettled(tasks);
3560
+ return { delivered };
3561
+ };
3499
3562
  // PLAN-ios-rich-dev-feedback-hydrate M4 (2026-05-13) — all-sessions
3500
3563
  // fan-out for process-wide envelope kinds (hud.segment). Iterates
3501
- // sessionPeers + delegates each session to activeAcpBroadcaster
3564
+ // sessionPeers and gates each attached connection independently
3502
3565
  // (consistent transport · same agent_thought_chunk text-in-text shape).
3503
3566
  activeAcpAllSessionsFeedbackBroadcaster = async (envProto) => {
3504
3567
  let fannedTo = 0;
3505
3568
  let delivered = 0;
3506
3569
  for (const [sessionId, peers] of sessionPeers) {
3507
3570
  if (peers.size === 0) continue;
3508
- fannedTo += 1;
3571
+ if (!awarePeers.isElanousAware(sessionId)) {
3572
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3573
+ continue;
3574
+ }
3509
3575
  const env = { ...envProto, sessionId } as FeedbackEnvelope;
3510
3576
  const text = formatElanousFeedbackEnvelope({ method: 'emit', payload: env });
3577
+ let any = false;
3511
3578
  for (const peer of peers) {
3579
+ if (!awarePeers.isPeerAware(peer)) {
3580
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
3581
+ continue;
3582
+ }
3583
+ any = true;
3512
3584
  try {
3513
3585
  await peer.sessionUpdate({
3514
3586
  sessionId,
@@ -3522,6 +3594,7 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3522
3594
  /* swallow — best-effort fan-out */
3523
3595
  }
3524
3596
  }
3597
+ if (any) fannedTo += 1;
3525
3598
  }
3526
3599
  return { delivered, fannedTo };
3527
3600
  };
@@ -3593,6 +3666,7 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3593
3666
  try { await transport.close(); } catch { /* ignore */ }
3594
3667
  acpServerDisposeSessions(sessions, dualRole);
3595
3668
  activeAcpBroadcaster = null;
3669
+ activeAcpFeedbackBroadcaster = null;
3596
3670
  activeAcpAskPusher = null;
3597
3671
  activeAcpAllSessionsFeedbackBroadcaster = null;
3598
3672
  activeAcpAllSessionsTermFrameBroadcaster = null;
@@ -3621,7 +3695,9 @@ export async function runAcpServer(opts: AcpServerOptions = {}): Promise<void> {
3621
3695
  } finally {
3622
3696
  acpServerDisposeSessions(sessions, dualRole);
3623
3697
  activeAcpBroadcaster = null;
3698
+ activeAcpFeedbackBroadcaster = null;
3624
3699
  activeAcpAskPusher = null;
3700
+ activeAcpAllSessionsFeedbackBroadcaster = null;
3625
3701
  activeAcpAllSessionsTermFrameBroadcaster = null;
3626
3702
  if (debug.enabled) {
3627
3703
  debug.log('acp.broadcaster.lifecycle', 'clear', { mode: 'stdio' });
@@ -30,6 +30,7 @@ import { surfaceEventsDbPath, openSurfaceEventsDb, recentSentDigest, recordInbou
30
30
  import { elanousSelfAccessPrompt, elanousSelfAmbientParts } from './self-ambient.js';
31
31
  import { localRefGroundingAmbient } from './ref-grounding.js';
32
32
  import { resolveRouteDecision } from '../llm/route-decision.js';
33
+ import { explicitTurnTier } from '../model-tier/turn-tier.js';
33
34
  import type { RouteDecision } from '../llm/route-decision.js';
34
35
  import { debug } from '../debug/log.js';
35
36
  import { setEventLoopActivity } from '../debug/event-loop-watchdog.js';
@@ -59,7 +60,7 @@ function recentSentContext(): string {
59
60
  * Bash + PtyShell + search), plus — when the finance domain pack is
60
61
  * enabled (A0) — the analyst orientation + resource map + first-class
61
62
  * finance tools. Drop-in for the hosting bot's `runTurnImpl`. */
62
- export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource): typeof runTurn {
63
+ export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource, runTurnImpl: typeof runTurn = runTurn): typeof runTurn {
63
64
  if (surface !== 'telegram' && surface !== 'discord') {
64
65
  throw new Error(`Unsupported elanous agent session source: ${surface}`);
65
66
  }
@@ -110,7 +111,13 @@ export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource)
110
111
  // ⛔ 호출자가 모델을 핀했으면 «건드리지 않는다». 실패는 fail-soft — config 기본을 유지한다.
111
112
  let llmOpts = opts.llmOpts;
112
113
  let routeDecision: RouteDecision | undefined;
113
- if (cfg.llm.provider === 'openai-codex') {
114
+ const explicitTier = opts.llmOpts?.model ? undefined : explicitTurnTier(cfg, opts.sessionId);
115
+ if (cfg.llm.provider === 'openai-codex' && explicitTier) {
116
+ llmOpts = { ...(llmOpts ?? {}), model: explicitTier.model };
117
+ debug.log('llm.route-decision', 'self-turn', {
118
+ surface, provider: cfg.llm.provider, model: explicitTier.model, skipped: 'explicit-tier',
119
+ });
120
+ } else if (cfg.llm.provider === 'openai-codex') {
114
121
  try {
115
122
  const decision = resolveRouteDecision({
116
123
  provider: cfg.llm.provider,
@@ -212,8 +219,19 @@ export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource)
212
219
  ...(surfaceFileSink ? { fileSink: surfaceFileSink } : {}),
213
220
  });
214
221
  llmOpts = terminal.llmOpts;
215
- return runTurn({
222
+ let currentMessage = '';
223
+ let sawToolCall = false;
224
+ return runTurnImpl({
216
225
  ...opts,
226
+ onDelta: (delta) => {
227
+ currentMessage += delta;
228
+ opts.onDelta?.(delta);
229
+ },
230
+ onToolCall: (call) => {
231
+ currentMessage = '';
232
+ sawToolCall = true;
233
+ opts.onToolCall?.(call);
234
+ },
217
235
  llmOpts,
218
236
  systemPrompt: terminal.systemPromptParts.join('\n\n'),
219
237
  tools: terminal.specs,
@@ -226,11 +244,12 @@ export function makeElanousAgentRunTurn(cfg: UserConfig, surface: SessionSource)
226
244
  // NEXT turn's memory_recall knows the task was CANCELLED, not silently
227
245
  // dropped or still running. Only turnAbort (=/cancel) aborts this signal.
228
246
  const wasAborted = opts.signal?.aborted === true;
247
+ const answer = sawToolCall ? currentMessage.trim() : result.text;
229
248
  const responseText = wasAborted
230
- ? (result.text?.trim()
231
- ? `${result.text}\n\n⏹️ 이 작업은 /cancel 로 취소되었습니다 (실행 중이던 Bash/PTY 종료됨).`
249
+ ? (answer?.trim()
250
+ ? `${answer}\n\n⏹️ 이 작업은 /cancel 로 취소되었습니다 (실행 중이던 Bash/PTY 종료됨).`
232
251
  : '⏹️ 작업이 /cancel 로 취소되었습니다. 실행 중이던 Bash/PTY 를 종료했습니다.')
233
- : result.text;
252
+ : answer;
234
253
  // Block 3 — 대화 턴을 크로스서피스 기억에 기록(질의+응답·direction=inbound).
235
254
  // fail-soft·비동기(대화 지연 없음). 이후 memory_recall 이 과거 대화도 회상.
236
255
  try { recordInboundTurn({ surface, userText: opts.userText, responseText, sessionId: opts.sessionId }); } catch { /* noop */ }
@@ -12,6 +12,7 @@
12
12
  // financeEnabled(cfg) 게이트(operator opt-in·미enable/미cfg 시 core 만·무회귀).
13
13
 
14
14
  import { buildCoreTools } from '../domains/core-tools.js';
15
+ import type { ToolRuntimeContext } from '../tool-runtime/types.js';
15
16
  import { buildFinanceTools } from '../domains/finance-tools.js';
16
17
  import { financeEnabled } from '../domains/finance.js';
17
18
  import { skillExecRuntime } from '../tool-runtime/skill-exec-runtime.js';
@@ -24,7 +25,7 @@ import type { LLMToolSpec } from '../llm.js';
24
25
  export interface SharedAppTools {
25
26
  specs: LLMToolSpec[];
26
27
  names: Set<string>;
27
- dispatch: (name: string, args: Record<string, unknown>) => Promise<unknown>;
28
+ dispatch: (name: string, args: Record<string, unknown>, context?: ToolRuntimeContext) => Promise<unknown>;
28
29
  }
29
30
 
30
31
  /** L2 core + L3 finance(gated) + skill discovery/execution 단일 조립. specs 순서 = core, finance, discovery, execution. */
@@ -35,9 +36,10 @@ export function buildSharedAppTools(cfg?: UserConfig): SharedAppTools {
35
36
  const skill = skillExecRuntime;
36
37
  const specs: LLMToolSpec[] = [...core.specs, ...(fin ? fin.specs : []), skillsList, skill.spec];
37
38
  const names = new Set<string>([...core.names, ...(fin ? fin.names : []), skillsList.name, skill.id]);
38
- const dispatch = async (name: string, args: Record<string, unknown>): Promise<unknown> =>
39
+ // context(B5) — 기억 조회 같은 긴 코어 도구가 진행 한 줄(emitFeedback)을 낼 수 있게 코어까지 넘긴다.
40
+ const dispatch = async (name: string, args: Record<string, unknown>, context?: ToolRuntimeContext): Promise<unknown> =>
39
41
  name === skillsList.name ? dispatchElanousSkillsList(args as ElanousSkillsListArgs) :
40
42
  name === skill.id ? skill.run(args as unknown as SkillExecArgs, { surface: 'skill' }) :
41
- fin && fin.names.has(name) ? fin.dispatch(name, args) : core.dispatch(name, args);
43
+ fin && fin.names.has(name) ? fin.dispatch(name, args) : core.dispatch(name, args, undefined, context);
42
44
  return { specs, names, dispatch };
43
45
  }
@@ -16,6 +16,7 @@ import { parseGoalType, type GoalType } from '../self-implement/goal-author.js';
16
16
  import type { DesignCheckOutcome } from '../design/design-check.js';
17
17
  import type { DesignGateResult } from '../design/design-gate.js';
18
18
  import type { PriorDraftFinding } from '../self-implement/prior-draft-findings.js';
19
+ import { parseShardIdentity, shardBoundaryBlock, withoutShardIdentity } from './shard-boundary-intent.js';
19
20
 
20
21
  export interface ReviewIntentInput {
21
22
  /** 무엇을 왜 — 골 원문. */
@@ -534,7 +535,9 @@ export function buildReviewIntent(i: Readonly<ReviewIntentInput>): string {
534
535
  // ⚠️ 목표 블록에는 **추출된 절을 뺀 나머지**를 넣는다(리뷰 should-fix) — 골 원문을 통째로 넣고
535
536
  // 그 안의 수용기준을 다시 목록으로 붙이면 **같은 내용이 두 번** 들어가 4000자 예산을 먹고,
536
537
  // 후순위 블록(스코프 경계)이 불필요하게 잘린다.
537
- const goalBody = stripExtractedSections(i.goal).trim();
538
+ const identity = parseShardIdentity(i.goal);
539
+ const shardBoundary = identity ? { title: '조각 경계', text: `조각 경계\n${shardBoundaryBlock(identity)}` } : undefined;
540
+ const goalBody = stripExtractedSections(identity ? withoutShardIdentity(i.goal) : i.goal).trim();
538
541
  const goal = goalBody ? { title: '목표', text: `목표\n${goalBody}` } : undefined;
539
542
  // ⭐ **자식이 낸 증거**(diff 밖 이행 · base 적색 · 직전 반영분)를 한 묶음으로 잡아 둔다 — 아래 예산
540
543
  // 배분에서 **목표 본문보다 먼저** 채우기 위해서다. 제목 문자열이 아니라 **동일성**으로 가른다.
@@ -563,6 +566,7 @@ export function buildReviewIntent(i: Readonly<ReviewIntentInput>): string {
563
566
  priorFindings,
564
567
  goalFile,
565
568
  goalType,
569
+ shardBoundary,
566
570
  goal,
567
571
  listBlock('수용기준', acceptance),
568
572
  applied,
@@ -584,7 +588,7 @@ export function buildReviewIntent(i: Readonly<ReviewIntentInput>): string {
584
588
  // `…[N개 생략됨]`** 이 됐다. 그러면 리뷰는 *"실행하지 않았다 · 증거가 없다"* 를 적고 — 그것이
585
589
  // **리뷰 입장에서 사실**이라 반박도 안 된다. 두 트랙 합쳐 **여섯 런**이 이 사인으로 죽었다.
586
590
  // ⭐ 목표 본문을 뒤로 미뤄도 잃는 것이 적다 — **수용기준·경계는 이미 추출돼 보호 블록**에 있다.
587
- const evidenceBlocks = [priorFindings, designGate, applied, preexisting, importerTestsNotRun, gateEvidence, designCheck, shardSiblings, coverage, claims, runFacts, goalFile].filter((b): b is Block => Boolean(b));
591
+ const evidenceBlocks = [shardBoundary, priorFindings, designGate, applied, preexisting, importerTestsNotRun, gateEvidence, designCheck, shardSiblings, coverage, claims, runFacts, goalFile].filter((b): b is Block => Boolean(b));
588
592
  const otherBlocks = blocks.filter((block) => !protectedBlocks.includes(block) && !evidenceBlocks.includes(block) && block !== goal);
589
593
  const order = [...protectedBlocks, ...evidenceBlocks, ...otherBlocks, ...(goal ? [goal] : [])];
590
594
  // ⛔⭐⭐⭐ 1차는 우선순위와 무관하게 공정 몫까지만 준다. 목표 본문은 이미 추출된
@@ -0,0 +1,70 @@
1
+ interface ShardIdentity {
2
+ readonly shardId: string;
3
+ readonly totalShards: number;
4
+ readonly position: number;
5
+ readonly summary: string;
6
+ readonly siblings: readonly { readonly shardId: string; readonly summary: string }[];
7
+ }
8
+
9
+ const SHARD_IDENTITY_SUFFIX = /(?:^|\n)## Shard identity\r?\n([\s\S]+?)\s*$/;
10
+ const MAX_BLOCK_CHARS = 800;
11
+
12
+ /** Read the JSON footer emitted by shardIdentityFeature, without treating other goal text as identity. */
13
+ export function parseShardIdentity(goalText: string): ShardIdentity | undefined {
14
+ const match = SHARD_IDENTITY_SUFFIX.exec(goalText);
15
+ if (!match) return undefined;
16
+ try {
17
+ const value: unknown = JSON.parse(match[1]!);
18
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
19
+ const identity = value as Record<string, unknown>;
20
+ if (typeof identity.shardId !== 'string' || !identity.shardId.trim()
21
+ || typeof identity.summary !== 'string'
22
+ || !Number.isInteger(identity.totalShards) || (identity.totalShards as number) < 1
23
+ || !Number.isInteger(identity.position) || (identity.position as number) < 1
24
+ || (identity.position as number) > (identity.totalShards as number)
25
+ || !Array.isArray(identity.siblings)
26
+ || !identity.siblings.every((sibling: unknown) => {
27
+ if (!sibling || typeof sibling !== 'object' || Array.isArray(sibling)) return false;
28
+ const item = sibling as Record<string, unknown>;
29
+ return typeof item.shardId === 'string' && !!item.shardId.trim() && typeof item.summary === 'string';
30
+ })) return undefined;
31
+ return identity as unknown as ShardIdentity;
32
+ } catch {
33
+ return undefined;
34
+ }
35
+ }
36
+
37
+ /** Remove only a successfully parsed footer; malformed metadata remains ordinary goal text. */
38
+ export function withoutShardIdentity(goalText: string): string {
39
+ return parseShardIdentity(goalText) ? goalText.replace(SHARD_IDENTITY_SUFFIX, '') : goalText;
40
+ }
41
+
42
+ function oneLine(value: string, limit: number): string {
43
+ const normalized = value.replace(/\s+/g, ' ').trim();
44
+ return normalized.length > limit ? `${normalized.slice(0, limit - 1)}…` : normalized;
45
+ }
46
+
47
+ /** Keep the sibling contract ahead of the goal body, with an explicit omission count. */
48
+ export function shardBoundaryBlock(identity: ShardIdentity): string {
49
+ const prefix = [
50
+ `이 PR 은 조각 ${oneLine(String(identity.position), 20)}/${oneLine(String(identity.totalShards), 20)}(${oneLine(identity.shardId, 80)}): ${oneLine(identity.summary, 240)}.`,
51
+ '형제 조각이 맡는 일(예: 등록·배선·화면)이 이 PR 에 없다는 이유로 must-fix 를 내지 말 것. 없어서 이 조각이 동작하지 않으면 should-fix 로 «형제 조각 <id> 에 의존» 한 줄.',
52
+ '형제 조각이 맡는 일:',
53
+ ];
54
+ const items = identity.siblings.map((sibling) => `- ${oneLine(sibling.shardId, 80)}: ${oneLine(sibling.summary, 240)}`);
55
+ const header = prefix.join('\n');
56
+ if (header.length > MAX_BLOCK_CHARS) return header.slice(0, MAX_BLOCK_CHARS);
57
+ const kept: string[] = [];
58
+ for (const [index, item] of items.entries()) {
59
+ const omitted = items.length - index - 1;
60
+ const marker = omitted ? `…외 ${omitted}` : '';
61
+ if ([...prefix, ...kept, item, ...(marker ? [marker] : [])].join('\n').length > MAX_BLOCK_CHARS) break;
62
+ kept.push(item);
63
+ }
64
+ const omitted = items.length - kept.length;
65
+ const marker = omitted ? `…외 ${omitted}` : '';
66
+ if (marker && header.length + marker.length + 1 > MAX_BLOCK_CHARS) {
67
+ return `${header.slice(0, MAX_BLOCK_CHARS - marker.length - 1)}\n${marker}`;
68
+ }
69
+ return [...prefix, ...kept, ...(marker ? [marker] : [])].join('\n');
70
+ }
@@ -55,7 +55,10 @@ export function githubInstallationCredential(deps: GithubAutomationTokenDeps = {
55
55
  const rel = join('secrets', 'github-app', 'app.json');
56
56
  const own = join(getElanousConfigDir(), rel);
57
57
  // An explicit config-dir override (tests · `--config-dir`) is a deliberate boundary — never reach past it.
58
- path = deps.configPath ?? (existsSync(own) || getElanousConfigDirOverride() ? own : join(prodInstanceRoot(), rel));
58
+ // ELANOUS_GITHUB_APP_CONFIG_PATH pins the machine-level fallback (the bun test preload points it at nothing,
59
+ // so tests never mint); an explicit config-dir override still wins.
60
+ const pinned = process.env.ELANOUS_GITHUB_APP_CONFIG_PATH?.trim();
61
+ path = deps.configPath ?? (getElanousConfigDirOverride() ? own : pinned || (existsSync(own) ? own : join(prodInstanceRoot(), rel)));
59
62
  }
60
63
  catch {
61
64
  try { debug.log('auth.github-app', 'token-failed', { reason: 'config-path-failed' }); } catch { /* fail open */ }
@@ -0,0 +1,151 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { appendFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import type { TrackAgentDecision, TrackAgentInput, TrackAgentResult } from './track-agent.js';
7
+
8
+ const exec = promisify(execFile);
9
+ const dateInKst = (now: Date): string => new Intl.DateTimeFormat('en-CA', {
10
+ timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit',
11
+ }).format(now);
12
+
13
+ export type ShadowLedgerEntry = {
14
+ at: string;
15
+ missionId: string;
16
+ taskId: string;
17
+ track: string;
18
+ event: 'decision-started' | 'decision' | 'orchestrate-started' | 'orchestrate';
19
+ action?: TrackAgentDecision['action'];
20
+ status?: TrackAgentResult['status'];
21
+ workdir?: string | null;
22
+ workdirs?: (string | null)[];
23
+ };
24
+
25
+ export type ShadowOptions = {
26
+ ledgerDir?: string;
27
+ now?: () => Date;
28
+ maxDecisionsPerDay?: number;
29
+ orchestrate?: (args: string[]) => Promise<{ stdout: string }>;
30
+ };
31
+
32
+ export function shadowLedgerPath(options: ShadowOptions = {}): string {
33
+ const now = options.now?.() ?? new Date();
34
+ return join(options.ledgerDir ?? join(effectiveInstanceRoot(), 'autopilot', 'track-agent-shadow'), `${dateInKst(now)}.jsonl`);
35
+ }
36
+
37
+ function append(entry: ShadowLedgerEntry, path: string): void {
38
+ mkdirSync(dirname(path), { recursive: true });
39
+ appendFileSync(path, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', flag: 'a' });
40
+ }
41
+
42
+ function entryFor(input: TrackAgentInput, event: ShadowLedgerEntry['event'], now: Date): ShadowLedgerEntry {
43
+ return { at: now.toISOString(), missionId: input.missionId, taskId: input.taskId, track: input.track, event };
44
+ }
45
+
46
+ /** Reserve a daily decision slot on disk before invoking the agent. An unreadable ledger never grants a slot. */
47
+ export async function decideShadowTrackAction(
48
+ input: TrackAgentInput,
49
+ decide: (input: TrackAgentInput) => Promise<TrackAgentDecision> | TrackAgentDecision,
50
+ options: ShadowOptions = {},
51
+ ): Promise<TrackAgentDecision> {
52
+ const now = options.now?.() ?? new Date();
53
+ const path = shadowLedgerPath({ ...options, now: () => now });
54
+ const configured = options.maxDecisionsPerDay ?? 20;
55
+ const limit = Number.isSafeInteger(configured) && configured >= 0 ? configured : 20;
56
+ const lockPath = `${path}.lock`;
57
+ try {
58
+ mkdirSync(dirname(path), { recursive: true });
59
+ // The lock spans only count + reservation, never the callback. A contested slot fails closed.
60
+ mkdirSync(lockPath);
61
+ } catch {
62
+ return { action: 'hold', reason: 'shadow 원장 확인 실패' };
63
+ }
64
+ let reserved = false;
65
+ let capped = false;
66
+ try {
67
+ let rows: string[];
68
+ try { rows = readFileSync(path, 'utf8').split('\n').filter(Boolean); }
69
+ catch (error) {
70
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
71
+ rows = [];
72
+ }
73
+ const count = rows.reduce((total, row) => {
74
+ const value: unknown = JSON.parse(row);
75
+ if (!value || typeof value !== 'object' || !('event' in value)) throw new Error('invalid shadow ledger');
76
+ return total + ((value as { event: unknown }).event === 'decision-started' ? 1 : 0);
77
+ }, 0);
78
+ if (count >= limit) capped = true;
79
+ else { append(entryFor(input, 'decision-started', now), path); reserved = true; }
80
+ } catch { /* The callback cannot run without a durable reservation. */ }
81
+ finally {
82
+ try { rmdirSync(lockPath); }
83
+ catch { reserved = false; }
84
+ }
85
+ if (capped) return { action: 'hold', reason: '일일 판정 상한' };
86
+ if (!reserved) return { action: 'hold', reason: 'shadow 원장 확인 실패' };
87
+ try {
88
+ const decision = await decide(input);
89
+ append({ ...entryFor(input, 'decision', now), action: decision.action }, path);
90
+ return decision;
91
+ } catch {
92
+ return { action: 'hold', reason: '판정 실패 — 사람 확인' };
93
+ }
94
+ }
95
+
96
+ function outcomeFromOutput(stdout: string): { workdirs: (string | null)[]; succeeded: boolean } {
97
+ const parsed: unknown = JSON.parse(stdout.trim());
98
+ const results = parsed && typeof parsed === 'object' && 'results' in parsed
99
+ ? (parsed as { results: unknown }).results : parsed;
100
+ const items: unknown[] = Array.isArray(results) ? results : [results];
101
+ const workdirs = items.map((item) => {
102
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return null;
103
+ const value = item as Record<string, unknown>;
104
+ const path = value.worktreePath ?? value.workdir;
105
+ return typeof path === 'string' && path.length > 0 ? path : null;
106
+ });
107
+ return {
108
+ workdirs,
109
+ succeeded: items.length > 0 && items.every((item, index) =>
110
+ item !== null && typeof item === 'object' && !Array.isArray(item)
111
+ && (item as Record<string, unknown>).status === 'done' && workdirs[index] !== null),
112
+ };
113
+ }
114
+
115
+ /** Called only after the ordinary track/forbidden-task/budget gates approve the work. */
116
+ export async function launchTrackAgentShadow(input: TrackAgentInput, options: ShadowOptions = {}): Promise<TrackAgentResult> {
117
+ const now = options.now?.() ?? new Date();
118
+ const path = shadowLedgerPath({ ...options, now: () => now });
119
+ try {
120
+ append(entryFor(input, 'orchestrate-started', now), path);
121
+ } catch { return { status: 'failed', detail: 'shadow 원장 기록 실패' }; }
122
+ const args = ['self', 'orchestrate', '--json', `${input.title}\n${input.prompt}`];
123
+ let status: TrackAgentResult['status'] = 'failed';
124
+ let workdirs: (string | null)[] = [];
125
+ try {
126
+ const run = options.orchestrate ?? (async (values: string[]) => {
127
+ const instanceRoot = effectiveInstanceRoot();
128
+ const { stdout } = await exec('bun', ['bin/elanous.mjs', '--config-dir', instanceRoot, ...values], {
129
+ cwd: process.env.ELANOUS_TOOL_CWD?.trim() || resolve(import.meta.dir, '../..'),
130
+ env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
131
+ encoding: 'utf8', maxBuffer: 1024 * 1024,
132
+ });
133
+ return { stdout };
134
+ });
135
+ const outcome = outcomeFromOutput((await run(args)).stdout);
136
+ workdirs = outcome.workdirs;
137
+ if (outcome.succeeded) status = 'launched';
138
+ } catch (error) {
139
+ // self orchestrate may exit nonzero after writing its JSON results; retain every observed workdir.
140
+ const stdout = (error as { stdout?: unknown }).stdout;
141
+ if (typeof stdout === 'string') {
142
+ try { workdirs = outcomeFromOutput(stdout).workdirs; } catch { /* malformed result has no observed workdir */ }
143
+ }
144
+ }
145
+ try {
146
+ append({ ...entryFor(input, 'orchestrate', now), status, workdir: workdirs[0] ?? null, workdirs }, path);
147
+ } catch { return { status: 'failed', detail: 'shadow 원장 기록 실패' }; }
148
+ return status === 'launched'
149
+ ? { status, detail: workdirs.join(', ') }
150
+ : { status, detail: 'shadow 실행 실패 또는 workdir 미관측' };
151
+ }