elanous 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/package.json +5 -5
  178. package/plugins/card-followup/README.md +13 -4
  179. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  180. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  181. package/plugins/card-followup/graphs/run-step.ts +122 -18
  182. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  183. package/plugins/card-followup/plugin.json +1 -1
  184. package/plugins/doc-draft/README.md +13 -0
  185. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  186. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  187. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  188. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  189. package/plugins/doc-draft/plugin.json +24 -0
  190. package/plugins/geo-check/README.md +3 -1
  191. package/plugins/geo-check/graphs/run-step.ts +24 -3
  192. package/plugins/geo-check/plugin.json +1 -1
  193. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  194. package/plugins/job-coach/README.md +1 -1
  195. package/plugins/job-coach/graphs/run-step.ts +2 -2
  196. package/plugins/job-coach/plugin.json +1 -1
  197. package/plugins/job-coach/plugin.ts +1 -1
  198. package/scripts/affinity/endcard.sh +3 -2
  199. package/scripts/affinity/reframe.sh +3 -1
  200. package/scripts/ci-public-export-leak-gate.ts +48 -0
  201. package/scripts/codex-quota-alert.ts +122 -258
  202. package/scripts/coord-tracks.json +4 -4
  203. package/scripts/field-feed/deliver-node.ts +52 -0
  204. package/scripts/field-feed/feed-node.ts +67 -0
  205. package/scripts/field-feed/lib.ts +162 -0
  206. package/scripts/field-feed/reel-node.ts +24 -0
  207. package/scripts/field-feed/start.ts +62 -0
  208. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  209. package/scripts/install.sh +92 -13
  210. package/scripts/landing-heal/clean-checkout.ts +114 -0
  211. package/scripts/landing-heal/run-mission.ts +38 -3
  212. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  213. package/scripts/public-export.ts +8 -4
  214. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  215. package/scripts/release-loop/env-known-failures.ts +31 -0
  216. package/scripts/release-loop/gate-node.ts +81 -19
  217. package/scripts/release-loop/known-issues-node.ts +159 -0
  218. package/scripts/release-loop/npm-publish-node.ts +118 -0
  219. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  220. package/scripts/release-loop/release-readiness.ts +58 -0
  221. package/scripts/release-loop/unattended-release.ts +93 -0
  222. package/scripts/ux/measure-first-text.ts +102 -0
  223. package/scripts/vflow-collect.ts +20 -7
  224. package/scripts/vflow-search.ts +73 -0
  225. package/scripts/video-free-line-test-walker.ts +36 -0
  226. package/scripts/video-pipeline.ts +2 -2
  227. package/skills/explainer-video/SKILL.md +12 -9
  228. package/skills/explainer-video/engine/build.mjs +1 -1
  229. package/skills/explainer-video/engine/reel.mjs +138 -31
  230. package/skills/explainer-video/engine/reel.sh +2 -1
  231. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  232. package/skills/grill-me/SKILL.md +1 -1
  233. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  234. package/src/a2a/server.ts +2 -0
  235. package/src/acp/elanous-aware-peers.ts +30 -0
  236. package/src/acp/server.ts +91 -15
  237. package/src/agent/monad-agent-turn.ts +25 -6
  238. package/src/agent/shared-app-tools.ts +5 -3
  239. package/src/agent-substrate/review-intent.ts +6 -2
  240. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  241. package/src/auth/github-app-token.ts +4 -1
  242. package/src/autopilot/track-agent-shadow.ts +151 -0
  243. package/src/autopilot/track-agent.ts +23 -3
  244. package/src/boot/acp-server-help.ts +16 -0
  245. package/src/boot/daemon-prompt-turn.ts +31 -10
  246. package/src/boot/daemon-tools/index.ts +7 -1
  247. package/src/budget/codex-credit-pace.ts +84 -0
  248. package/src/budget/codex-credit-plan.ts +154 -0
  249. package/src/budget/codex-reset-credit-state.ts +2 -0
  250. package/src/chat/chat-cli.ts +4 -1
  251. package/src/chat/short-question.ts +40 -0
  252. package/src/cli/agent-cli.ts +24 -15
  253. package/src/cli/ask-bare.ts +42 -0
  254. package/src/cli/cli-user-error.ts +25 -0
  255. package/src/cli/codex-credit-plan-view.ts +25 -0
  256. package/src/cli/dependence-gauge.ts +237 -0
  257. package/src/cli/doctor-cli.ts +5 -0
  258. package/src/cli/doctor-modes.ts +48 -0
  259. package/src/cli/intake-cli.ts +12 -2
  260. package/src/cli/intake-source-cli.ts +58 -0
  261. package/src/cli/machine-cli.ts +23 -0
  262. package/src/cli/msg-cli.ts +137 -0
  263. package/src/cli/plugin-cli.ts +25 -0
  264. package/src/cli/pr-cli.ts +36 -0
  265. package/src/cli/provider-cli.ts +5 -0
  266. package/src/cli/release-cli.ts +42 -1
  267. package/src/cli/release-run-if-ready.ts +48 -0
  268. package/src/cli/setup-cli.ts +27 -6
  269. package/src/cli/setup-recommend.ts +8 -0
  270. package/src/cli/usage-cli.ts +11 -1
  271. package/src/codex/setup.ts +27 -8
  272. package/src/dashboard/index.ts +1 -1
  273. package/src/discord.ts +56 -6
  274. package/src/domains/core-tools.ts +37 -14
  275. package/src/domains/outbound-alert.ts +50 -11
  276. package/src/exec-requests/planner.ts +140 -0
  277. package/src/exec-requests/runner.ts +338 -0
  278. package/src/exec-requests/store.ts +76 -0
  279. package/src/field/field-media.ts +8 -3
  280. package/src/field/field-reel.ts +196 -0
  281. package/src/grok/credential.ts +7 -5
  282. package/src/harness/aside-run-status.ts +33 -0
  283. package/src/harness/browser-verify.ts +48 -17
  284. package/src/harness/harness-cli-command.ts +25 -3
  285. package/src/harness/image-load-state.ts +21 -0
  286. package/src/harness/process-garbage.ts +45 -0
  287. package/src/index.ts +86 -14
  288. package/src/instance/leader.ts +13 -1
  289. package/src/intake-plane/collect-github.ts +5 -3
  290. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  291. package/src/intake-plane/discord-seat-work.ts +56 -0
  292. package/src/intake-plane/intake-sources.ts +202 -0
  293. package/src/intake-plane/items.ts +28 -12
  294. package/src/intake-plane/seat-doc-route.ts +207 -0
  295. package/src/intake-plane/submit-intake-work.ts +9 -1
  296. package/src/intake-plane/telegram-seat-work.ts +72 -0
  297. package/src/llm/model-defaults.ts +24 -1
  298. package/src/llm.ts +5 -4
  299. package/src/machines/machine-ledger.ts +127 -0
  300. package/src/market/pack-internal-refs.ts +28 -0
  301. package/src/market/publish.ts +31 -4
  302. package/src/maturity/feature-maturity.ts +65 -0
  303. package/src/model-tier/turn-tier.ts +24 -0
  304. package/src/msg/msg-store.ts +149 -0
  305. package/src/native-tool-catalog.ts +2 -1
  306. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  307. package/src/nexus/api/consult-requests.ts +117 -0
  308. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  309. package/src/nexus/api/exec-requests.ts +51 -0
  310. package/src/nexus/api/field-uploads.ts +40 -3
  311. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  312. package/src/nexus/api/graph-approvals.ts +71 -4
  313. package/src/nexus/api/http-server.ts +61 -7
  314. package/src/nexus/api/seat-requests.ts +254 -0
  315. package/src/nexus/api/trace-run-scan.ts +37 -0
  316. package/src/nexus/api/trace.ts +19 -3
  317. package/src/nexus/index.ts +3 -0
  318. package/src/nexus/setup-status.ts +4 -5
  319. package/src/notify/llm-budget-alert.ts +16 -10
  320. package/src/oauth/codex-account-rotation.ts +26 -4
  321. package/src/oauth/codex-account-store.ts +20 -2
  322. package/src/oauth/codex-browser-login.ts +173 -0
  323. package/src/oauth/codex.ts +4 -1
  324. package/src/oauth/fallback-chain.ts +5 -5
  325. package/src/oauth/store.ts +42 -5
  326. package/src/onboarding/continue-here.ts +29 -0
  327. package/src/onboarding/entry-hints.ts +8 -0
  328. package/src/onboarding/full-screen-io.ts +35 -3
  329. package/src/onboarding/help/skills.md +1 -1
  330. package/src/onboarding/non-interactive.ts +1 -0
  331. package/src/onboarding/subscription-hint.ts +27 -0
  332. package/src/onboarding/web-first.ts +83 -0
  333. package/src/onboarding.ts +230 -23
  334. package/src/plugins/maker/node-install.ts +92 -0
  335. package/src/plugins/maker/node-maker.ts +139 -0
  336. package/src/plugins/maker/plugin-maker.ts +19 -7
  337. package/src/policy/llm-policy.ts +268 -0
  338. package/src/prompt-library/session-guidance.ts +7 -1
  339. package/src/prompt-library/universal-preamble.ts +2 -2
  340. package/src/relay/relay-cli.ts +20 -0
  341. package/src/relay/relay-server.ts +220 -0
  342. package/src/release-loop/checklist.ts +9 -8
  343. package/src/self-dev/decompose.ts +1 -0
  344. package/src/self-dev/dev-pipeline.ts +6 -3
  345. package/src/self-dev/run-record-mirror.ts +44 -0
  346. package/src/self-dev/run-store.ts +12 -3
  347. package/src/self-implement/harness-policy-gates.ts +4 -1
  348. package/src/self-implement/report-origin.ts +12 -1
  349. package/src/session/chat.ts +17 -5
  350. package/src/skills/shared-agent-skill-roots.ts +25 -0
  351. package/src/steward/failure-streak.ts +78 -0
  352. package/src/steward/steward-cards.ts +24 -0
  353. package/src/steward/triage-budget.ts +127 -0
  354. package/src/steward/triage-plan.ts +75 -0
  355. package/src/steward/triage.ts +244 -42
  356. package/src/task-cards/card-store.ts +1 -1
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  358. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  359. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  360. package/src/telegram.ts +38 -2
  361. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  362. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  363. package/src/user-config.ts +80 -4
  364. package/src/version/packed-revision.json +1 -1
  365. package/src/video-pipeline/recipes/character.ts +42 -5
  366. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  367. package/src/video-pipeline/recipes/free-line.ts +16 -3
  368. package/src/video-pipeline/recipes/remote.ts +7 -1
  369. package/src/video-pipeline/vflow-reference.ts +109 -0
  370. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  379. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  448. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  449. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  450. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
package/src/acp/server.ts CHANGED
@@ -66,6 +66,7 @@ import {
66
66
  type ElanousTermPayload,
67
67
  } from './elanous-extensions.js';
68
68
  import { createMissionTurnEmitter } from './mission-turn-emit.js';
69
+ import { ElanousAwarePeers } from './elanous-aware-peers.js';
69
70
  import { enqueuePendingUserInput } from '../session/pending-input.js';
70
71
  import {
71
72
  ELANOUS_ASK_CANCEL_METHOD,
@@ -879,6 +880,10 @@ let activeAcpBroadcaster:
879
880
  | ((sessionId: string, update: unknown) => Promise<{ delivered: number }>)
880
881
  | null = null;
881
882
 
883
+ let activeAcpFeedbackBroadcaster:
884
+ | ((sessionId: string, env: FeedbackEnvelope) => Promise<{ delivered: number }>)
885
+ | null = null;
886
+
882
887
  // P0b(DESIGN-cross-surface-autonomy-membrane §10) — ACP ask pusher(elanous/ask → iPhone/PWA 시트)를 모듈-레벨로
883
888
  // 노출. daemon-runtime 이 코어 데몬 턴(SelfImplement dispatch)에 SurfaceUx confirm/question 채널을 주입할 때
884
889
  // createAcp*Channel(sessionId, getActiveAcpAskPusher()) 로 사용. sessionPeers 로 라우팅(연결 무관)·fail-soft(null=drop).
@@ -894,8 +899,8 @@ export function getActiveAcpBroadcaster():
894
899
  }
895
900
 
896
901
  /** 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
902
+ * adapter that fans out a `FeedbackEnvelope` only to peers advertising
903
+ * `_meta.elanous`, as a `elanous/feedback/emit` envelope packaged
899
904
  * inside an `agent_thought_chunk` sessionUpdate.
900
905
  *
901
906
  * Why `agent_thought_chunk` instead of a native `sessionUpdate:
@@ -912,15 +917,7 @@ export function getActiveAcpBroadcaster():
912
917
  export function getActiveAcpFeedbackBroadcaster():
913
918
  | ((sessionId: string, env: FeedbackEnvelope) => Promise<{ delivered: number }>)
914
919
  | 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
- };
920
+ return activeAcpFeedbackBroadcaster;
924
921
  }
925
922
 
926
923
  /** PLAN-ios-rich-dev-feedback-hydrate M4 (HUD) — fan-out across ALL
@@ -989,6 +986,9 @@ interface AcpServerContext {
989
986
  * fan-out walks the set so multi-surface clients (e.g. web PWA
990
987
  * + TUI on the same id) all see the same stream. */
991
988
  sessionPeers: Map<string, Set<SessionPeer>>;
989
+ awarePeers: ElanousAwarePeers<SessionPeer>;
990
+ /** One skip diagnostic per session, cleared when its last peer leaves. */
991
+ plainEnvelopeLogged: Set<string>;
992
992
  dualRole: DualRoleManager;
993
993
  /** Mints the unique suffix of a new session id. Injected so tests can
994
994
  * pin it deterministically — production wires `mintAcpSessionToken`. */
@@ -1029,6 +1029,21 @@ interface SessionPeer {
1029
1029
  peerId: string | null;
1030
1030
  }
1031
1031
 
1032
+ function isFeedbackTextUpdate(update: unknown): boolean {
1033
+ if (!update || typeof update !== 'object') return false;
1034
+ const u = update as { sessionUpdate?: unknown; content?: { type?: unknown; text?: unknown } };
1035
+ return u.sessionUpdate === 'agent_thought_chunk'
1036
+ && u.content?.type === 'text'
1037
+ && typeof u.content.text === 'string'
1038
+ && u.content.text.startsWith('[elanous/feedback/emit] ');
1039
+ }
1040
+
1041
+ function logSkippedPlainEnvelope(ctx: AcpServerContext, sessionId: string, kind: string): void {
1042
+ if (ctx.plainEnvelopeLogged.has(sessionId)) return;
1043
+ ctx.plainEnvelopeLogged.add(sessionId);
1044
+ debug.log('acp.envelope', 'skipped-plain-client', { sessionId, kind });
1045
+ }
1046
+
1032
1047
  /** Wire a single `AgentSideConnection` onto the provided ndJsonStream.
1033
1048
  * Owns all per-connection state (clientUiCaps, boundConnection ref,
1034
1049
  * per-peer `AcpServerHandle`) so stdio and transport paths diverge
@@ -1038,7 +1053,7 @@ function wireAcpConnection(
1038
1053
  stream: ReturnType<typeof ndJsonStream>,
1039
1054
  ctx: AcpServerContext,
1040
1055
  ): AgentSideConnection {
1041
- const { sessions, sessionPeers, dualRole, opts } = ctx;
1056
+ const { sessions, sessionPeers, awarePeers, plainEnvelopeLogged, dualRole, opts } = ctx;
1042
1057
  // 2026-05-13 (M2 of AskUserQuestion cross-surface) — bound connection
1043
1058
  // widened to expose `extMethod` + `extNotification` (SDK's elanous
1044
1059
  // extension escape hatch). pushAskRequest / pushAskCancel route through
@@ -1132,6 +1147,10 @@ function wireAcpConnection(
1132
1147
  }
1133
1148
  return { delivered: 0 };
1134
1149
  }
1150
+ if (isFeedbackTextUpdate(update) && !awarePeers.isElanousAware(sessionId)) {
1151
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
1152
+ return { delivered: 0 };
1153
+ }
1135
1154
  let delivered = 0;
1136
1155
  let dedupedByPeerId = 0;
1137
1156
  const tasks: Promise<unknown>[] = [];
@@ -1141,6 +1160,10 @@ function wireAcpConnection(
1141
1160
  // 은 모두 distinct peer 으로 취급해 send (degraded compat).
1142
1161
  const seenPeerIds = new Set<string>();
1143
1162
  for (const p of peers) {
1163
+ if (isFeedbackTextUpdate(update) && !awarePeers.isPeerAware(p)) {
1164
+ logSkippedPlainEnvelope(ctx, sessionId, 'feedback');
1165
+ continue;
1166
+ }
1144
1167
  if (opts2?.uiGate && !p.getUiCaps()[opts2.uiGate]) continue;
1145
1168
  if (opts2?.termGate && !p.getTermCaps()[opts2.termGate]) continue;
1146
1169
  if (p.peerId !== null) {
@@ -1380,6 +1403,7 @@ function wireAcpConnection(
1380
1403
  clientUiCaps = parsed.ui;
1381
1404
  clientTermCaps = parsed.term;
1382
1405
  clientAskCaps = parsed.ask;
1406
+ awarePeers.markPeer(peer, req.clientCapabilities);
1383
1407
  debug.log('acp.session', 'initialize', {
1384
1408
  protocolVersion: req.protocolVersion,
1385
1409
  clientName: req.clientInfo?.name ?? 'absent',
@@ -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
+ }