elanous 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.env.example +10 -7
  2. package/README.md +9 -4
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{8617.465ca22a319c95b3.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{8918-482b30ce2a8ebf4c.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{f6f3850d-7f324c6566002113.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/{page-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-2bf59c720fe9542c.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/icons/elanous-16.png +0 -0
  123. package/apps/pwa/out/icons/elanous-180.png +0 -0
  124. package/apps/pwa/out/icons/elanous-192.png +0 -0
  125. package/apps/pwa/out/icons/elanous-32.png +0 -0
  126. package/apps/pwa/out/icons/elanous-512.png +0 -0
  127. package/apps/pwa/out/index.html +1 -1
  128. package/apps/pwa/out/index.txt +21 -21
  129. package/apps/pwa/out/intake/index.html +1 -1
  130. package/apps/pwa/out/intake/index.txt +22 -22
  131. package/apps/pwa/out/live/index.html +1 -1
  132. package/apps/pwa/out/live/index.txt +22 -22
  133. package/apps/pwa/out/market/index.html +1 -1
  134. package/apps/pwa/out/market/index.txt +21 -21
  135. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  136. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  137. package/apps/pwa/out/morning/index.html +1 -1
  138. package/apps/pwa/out/morning/index.txt +22 -22
  139. package/apps/pwa/out/observatory/index.html +1 -1
  140. package/apps/pwa/out/observatory/index.txt +22 -22
  141. package/apps/pwa/out/reflection/index.html +1 -1
  142. package/apps/pwa/out/reflection/index.txt +22 -22
  143. package/apps/pwa/out/scheduler/index.html +1 -1
  144. package/apps/pwa/out/scheduler/index.txt +22 -22
  145. package/apps/pwa/out/sessions/index.html +1 -1
  146. package/apps/pwa/out/sessions/index.txt +21 -21
  147. package/apps/pwa/out/settings/devices/index.html +1 -1
  148. package/apps/pwa/out/settings/devices/index.txt +22 -22
  149. package/apps/pwa/out/settings/index.html +1 -1
  150. package/apps/pwa/out/settings/index.txt +22 -22
  151. package/apps/pwa/out/setup/done/index.html +1 -1
  152. package/apps/pwa/out/setup/done/index.txt +22 -22
  153. package/apps/pwa/out/setup/index.html +1 -1
  154. package/apps/pwa/out/setup/index.txt +22 -22
  155. package/apps/pwa/out/share/index.html +1 -1
  156. package/apps/pwa/out/share/index.txt +22 -22
  157. package/apps/pwa/out/showroom/index.html +1 -1
  158. package/apps/pwa/out/showroom/index.txt +21 -21
  159. package/apps/pwa/out/tasks/index.html +1 -1
  160. package/apps/pwa/out/tasks/index.txt +22 -22
  161. package/apps/pwa/out/term/index.html +1 -1
  162. package/apps/pwa/out/term/index.txt +22 -22
  163. package/apps/pwa/out/trace/index.html +1 -1
  164. package/apps/pwa/out/trace/index.txt +22 -22
  165. package/apps/pwa/out/vault/index.html +1 -1
  166. package/apps/pwa/out/vault/index.txt +22 -22
  167. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  168. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  169. package/apps/pwa/out/workflows/index.html +1 -1
  170. package/apps/pwa/out/workflows/index.txt +22 -22
  171. package/apps/pwa/out/workspace/index.html +1 -1
  172. package/apps/pwa/out/workspace/index.txt +22 -22
  173. package/apps/pwa/out/worktrees/index.html +1 -1
  174. package/apps/pwa/out/worktrees/index.txt +22 -22
  175. package/bin/elanous.cjs +107 -0
  176. package/graphs/field/field-feed.yaml +24 -0
  177. package/graphs/field/recipes.yaml +12 -0
  178. package/graphs/landing/landing-heal.yaml +3 -1
  179. package/graphs/landing/recipes.yaml +3 -0
  180. package/graphs/release/recipes.yaml +13 -0
  181. package/graphs/release/release-loop.yaml +13 -4
  182. package/package.json +5 -5
  183. package/plugins/card-followup/README.md +42 -0
  184. package/plugins/card-followup/examples/card-sample.json +7 -0
  185. package/plugins/card-followup/graphs/card-followup.yaml +18 -0
  186. package/plugins/card-followup/graphs/recipes.yaml +16 -0
  187. package/plugins/card-followup/graphs/run-step.ts +276 -0
  188. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  189. package/plugins/card-followup/plugin.json +25 -0
  190. package/plugins/doc-draft/README.md +13 -0
  191. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  192. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  193. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  194. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  195. package/plugins/doc-draft/plugin.json +24 -0
  196. package/plugins/geo-check/README.md +15 -0
  197. package/plugins/geo-check/examples/brand-sample.json +8 -0
  198. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  199. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  200. package/plugins/geo-check/graphs/run-step.ts +198 -0
  201. package/plugins/geo-check/plugin.json +24 -0
  202. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  203. package/plugins/job-coach/README.md +31 -1
  204. package/plugins/job-coach/graphs/run-step.ts +2 -2
  205. package/plugins/job-coach/plugin.json +1 -1
  206. package/plugins/job-coach/plugin.ts +1 -1
  207. package/scripts/affinity/endcard.sh +3 -2
  208. package/scripts/affinity/reframe.sh +3 -1
  209. package/scripts/ci-public-export-leak-gate.ts +48 -0
  210. package/scripts/codex-quota-alert.ts +122 -258
  211. package/scripts/coord-tracks.json +4 -4
  212. package/scripts/daemon-port-baseline.txt +1 -2
  213. package/scripts/field-feed/deliver-node.ts +52 -0
  214. package/scripts/field-feed/feed-node.ts +67 -0
  215. package/scripts/field-feed/lib.ts +162 -0
  216. package/scripts/field-feed/reel-node.ts +24 -0
  217. package/scripts/field-feed/start.ts +62 -0
  218. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  219. package/scripts/install.sh +92 -13
  220. package/scripts/landing-heal/clean-checkout.ts +114 -0
  221. package/scripts/landing-heal/run-mission.ts +38 -3
  222. package/scripts/lib/android-test-device.ts +32 -0
  223. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  224. package/scripts/public-export.ts +8 -4
  225. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  226. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  227. package/scripts/release-loop/env-known-failures.ts +31 -0
  228. package/scripts/release-loop/gate-diff.ts +29 -0
  229. package/scripts/release-loop/gate-node.ts +124 -29
  230. package/scripts/release-loop/known-issues-node.ts +159 -0
  231. package/scripts/release-loop/npm-publish-node.ts +118 -0
  232. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  233. package/scripts/release-loop/release-readiness.ts +58 -0
  234. package/scripts/release-loop/shard-plan.ts +47 -0
  235. package/scripts/release-loop/unattended-release.ts +93 -0
  236. package/scripts/ux/measure-first-text.ts +102 -0
  237. package/scripts/ux/record-page.ts +4 -1
  238. package/scripts/vflow-collect.ts +20 -7
  239. package/scripts/vflow-search.ts +73 -0
  240. package/scripts/video-free-line-test-walker.ts +36 -0
  241. package/scripts/video-pipeline.ts +2 -2
  242. package/skills/explainer-video/SKILL.md +24 -3
  243. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
  244. package/skills/explainer-video/engine/build.mjs +2 -2
  245. package/skills/explainer-video/engine/reel.mjs +235 -0
  246. package/skills/explainer-video/engine/reel.sh +9 -0
  247. package/skills/explainer-video/engine/vo.ts +58 -16
  248. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  249. package/skills/grill-me/SKILL.md +1 -1
  250. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  251. package/src/a2a/server.ts +2 -0
  252. package/src/acp/elanous-aware-peers.ts +30 -0
  253. package/src/acp/server.ts +91 -15
  254. package/src/agent/monad-agent-turn.ts +25 -6
  255. package/src/agent/shared-app-tools.ts +5 -3
  256. package/src/agent-substrate/review-intent.ts +6 -2
  257. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  258. package/src/auth/github-app-token.ts +4 -1
  259. package/src/autopilot/mission-origin.ts +20 -4
  260. package/src/autopilot/mission-phase-track.ts +11 -1
  261. package/src/autopilot/track-agent-shadow.ts +151 -0
  262. package/src/autopilot/track-agent.ts +26 -6
  263. package/src/autopilot/track-courier.ts +2 -2
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/chat/chat-cli.ts +4 -1
  271. package/src/chat/short-question.ts +40 -0
  272. package/src/cli/agent-cli.ts +24 -15
  273. package/src/cli/ask-bare.ts +42 -0
  274. package/src/cli/cli-user-error.ts +25 -0
  275. package/src/cli/codex-credit-plan-view.ts +25 -0
  276. package/src/cli/dependence-gauge.ts +237 -0
  277. package/src/cli/doctor-cli.ts +5 -0
  278. package/src/cli/doctor-modes.ts +48 -0
  279. package/src/cli/intake-cli.ts +12 -2
  280. package/src/cli/intake-source-cli.ts +58 -0
  281. package/src/cli/machine-cli.ts +23 -0
  282. package/src/cli/msg-cli.ts +137 -0
  283. package/src/cli/nexus-ios-bind.ts +25 -8
  284. package/src/cli/phone-link.ts +117 -0
  285. package/src/cli/plugin-cli.ts +52 -0
  286. package/src/cli/pr-cli.ts +36 -0
  287. package/src/cli/private-ledger-commands.ts +31 -0
  288. package/src/cli/provider-cli.ts +5 -0
  289. package/src/cli/release-cli.ts +56 -12
  290. package/src/cli/release-run-if-ready.ts +48 -0
  291. package/src/cli/setup-cli.ts +27 -6
  292. package/src/cli/setup-recommend.ts +8 -0
  293. package/src/cli/usage-cli.ts +11 -1
  294. package/src/codex/setup.ts +27 -8
  295. package/src/dashboard/index.ts +1 -1
  296. package/src/discord.ts +56 -6
  297. package/src/domains/core-tools.ts +37 -14
  298. package/src/domains/outbound-alert.ts +55 -16
  299. package/src/exec-requests/planner.ts +140 -0
  300. package/src/exec-requests/runner.ts +338 -0
  301. package/src/exec-requests/store.ts +76 -0
  302. package/src/field/field-media.ts +250 -0
  303. package/src/field/field-reel.ts +196 -0
  304. package/src/grok/credential.ts +7 -5
  305. package/src/harness/aside-run-status.ts +33 -0
  306. package/src/harness/browser-verify.ts +48 -17
  307. package/src/harness/harness-cli-command.ts +25 -3
  308. package/src/harness/image-load-state.ts +21 -0
  309. package/src/harness/process-garbage.ts +45 -0
  310. package/src/index.ts +104 -38
  311. package/src/instance/leader.ts +13 -1
  312. package/src/intake-plane/collect-github.ts +5 -3
  313. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  314. package/src/intake-plane/discord-seat-work.ts +56 -0
  315. package/src/intake-plane/intake-sources.ts +202 -0
  316. package/src/intake-plane/items.ts +28 -12
  317. package/src/intake-plane/seat-doc-route.ts +207 -0
  318. package/src/intake-plane/submit-intake-work.ts +9 -1
  319. package/src/intake-plane/telegram-seat-work.ts +72 -0
  320. package/src/llm/model-defaults.ts +24 -1
  321. package/src/llm.ts +5 -4
  322. package/src/machines/machine-ledger.ts +127 -0
  323. package/src/market/pack-internal-refs.ts +28 -0
  324. package/src/market/publish.ts +31 -4
  325. package/src/maturity/feature-maturity.ts +65 -0
  326. package/src/model-tier/turn-tier.ts +24 -0
  327. package/src/msg/msg-store.ts +149 -0
  328. package/src/native-tool-catalog.ts +2 -1
  329. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  330. package/src/nexus/api/consult-requests.ts +117 -0
  331. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  332. package/src/nexus/api/exec-requests.ts +51 -0
  333. package/src/nexus/api/field-uploads.ts +180 -0
  334. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  335. package/src/nexus/api/graph-approvals.ts +120 -4
  336. package/src/nexus/api/harness-api.ts +219 -5
  337. package/src/nexus/api/http-server.ts +68 -5
  338. package/src/nexus/api/seat-requests.ts +254 -0
  339. package/src/nexus/api/trace-run-scan.ts +37 -0
  340. package/src/nexus/api/trace.ts +19 -3
  341. package/src/nexus/index.ts +3 -0
  342. package/src/nexus/setup-status.ts +4 -5
  343. package/src/notify/llm-budget-alert.ts +125 -0
  344. package/src/oauth/codex-account-rotation.ts +26 -4
  345. package/src/oauth/codex-account-store.ts +20 -2
  346. package/src/oauth/codex-browser-login.ts +173 -0
  347. package/src/oauth/codex.ts +4 -1
  348. package/src/oauth/fallback-chain.ts +5 -5
  349. package/src/oauth/store.ts +42 -5
  350. package/src/onboarding/continue-here.ts +29 -0
  351. package/src/onboarding/entry-hints.ts +8 -0
  352. package/src/onboarding/full-screen-io.ts +35 -3
  353. package/src/onboarding/help/skills.md +1 -1
  354. package/src/onboarding/non-interactive.ts +1 -0
  355. package/src/onboarding/subscription-hint.ts +27 -0
  356. package/src/onboarding/web-first.ts +83 -0
  357. package/src/onboarding.ts +230 -23
  358. package/src/plugins/maker/node-install.ts +92 -0
  359. package/src/plugins/maker/node-maker.ts +139 -0
  360. package/src/plugins/maker/plugin-maker.ts +220 -0
  361. package/src/policy/llm-policy.ts +268 -0
  362. package/src/prompt-library/session-guidance.ts +7 -1
  363. package/src/prompt-library/universal-preamble.ts +2 -2
  364. package/src/relay/channel.ts +184 -0
  365. package/src/relay/mux.ts +182 -0
  366. package/src/relay/relay-cli.ts +20 -0
  367. package/src/relay/relay-server.ts +220 -0
  368. package/src/relay/test-vectors.json +29 -0
  369. package/src/release-loop/checklist.ts +36 -9
  370. package/src/seat-address/seat-address.ts +34 -0
  371. package/src/self-dev/decompose.ts +1 -0
  372. package/src/self-dev/dev-pipeline.ts +6 -3
  373. package/src/self-dev/run-record-mirror.ts +44 -0
  374. package/src/self-dev/run-store.ts +12 -3
  375. package/src/self-implement/harness-policy-gates.ts +4 -1
  376. package/src/self-implement/report-origin.ts +12 -1
  377. package/src/self-implement/run-ledger-compact.ts +1 -1
  378. package/src/session/chat.ts +17 -5
  379. package/src/session/index.ts +23 -10
  380. package/src/skills/shared-agent-skill-roots.ts +25 -0
  381. package/src/steward/failure-streak.ts +78 -0
  382. package/src/steward/steward-cards.ts +24 -0
  383. package/src/steward/triage-budget.ts +127 -0
  384. package/src/steward/triage-plan.ts +75 -0
  385. package/src/steward/triage.ts +244 -42
  386. package/src/task-cards/card-store.ts +1 -1
  387. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  388. package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
  389. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  390. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  391. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  392. package/src/telegram.ts +145 -2
  393. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  394. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  395. package/src/user-config.ts +84 -4
  396. package/src/version/packed-revision.json +1 -1
  397. package/src/video-pipeline/recipes/character.ts +42 -5
  398. package/src/video-pipeline/recipes/explainer.ts +213 -0
  399. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  400. package/src/video-pipeline/recipes/free-line.ts +16 -3
  401. package/src/video-pipeline/recipes/remote.ts +7 -1
  402. package/src/video-pipeline/vflow-reference.ts +109 -0
  403. package/src/video-pipeline/walk-line.ts +3 -2
  404. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
  411. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
  419. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
  477. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  478. package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
  479. /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -6,8 +6,9 @@ import { debug, redactSecretText } from '../debug/log.js';
6
6
  import { getOrCreatePersonaSession } from '../session/index.js';
7
7
  import { getUserConfig } from '../user-config.js';
8
8
  import { decideBudget, readBudgetInputsLive } from '../self-implement/budget-gate.js';
9
- import { loadTrackRegistry } from './mission-phase-track.js';
9
+ import { findTrack } from './mission-phase-track.js';
10
10
  import { effectiveInstanceRoot } from '../instance/resolve.js';
11
+ import { decideShadowTrackAction, launchTrackAgentShadow, type ShadowOptions } from './track-agent-shadow.js';
11
12
 
12
13
  export type TrackAgentInput = { missionId: string; taskId: string; title: string; prompt: string; track: string };
13
14
  export type TrackAgentDecision = { action: 'say' | 'hold'; reason?: string };
@@ -25,6 +26,8 @@ type RunnerDeps = {
25
26
  session?: (persona: string) => string;
26
27
  root?: string;
27
28
  maxConcurrent?: number;
29
+ shadow?: boolean;
30
+ shadowOptions?: ShadowOptions;
28
31
  };
29
32
  const root = resolve(import.meta.dir, '../..');
30
33
  // The installed package is not a git checkout — the daemon's target repository comes first.
@@ -41,11 +44,12 @@ const runCli = async (args: string[]): Promise<{ stdout: string }> => {
41
44
  const { argv, env } = universeLaunch(args);
42
45
  return exec('bun', argv, { cwd: repoDir(), env, timeout: 120_000, maxBuffer: 1024 * 1024, encoding: 'utf8' });
43
46
  };
44
- const forbidden = /\b(?:delete|remove|deploy|publish|release|restart|reboot|merge|force|pay|purchase|secret|credential|sudo|rm\s+-rf|config\s+set)\b|(?:^|\s)--prod\b|삭제|배포|게시|릴리스|재시작|재부팅|병합|결제|자격|비밀|강제/i;
47
+ const forbiddenActionRegex = /\b(?:delete|remove|deploy|publish|release|restart|reboot|merge|force|pay|purchase|secret|credential|sudo|rm\s+-rf|config\s+set)\b|(?:^|\s)--prod\b|삭제|배포|게시|릴리스|재시작|재부팅|병합|결제|자격|비밀|강제/i;
48
+ export const TRACK_AGENT_FORBIDDEN_ACTION_REGEX = new RegExp(forbiddenActionRegex.source, forbiddenActionRegex.flags);
45
49
  let running = 0;
46
50
 
47
51
  function context(input: TrackAgentInput, repo: string): string {
48
- const track = loadTrackRegistry().find(({ id }) => id === input.track);
52
+ const track = findTrack(input.track);
49
53
  if (!track) throw new Error('unknown track');
50
54
  const docs = resolve(repo, 'docs');
51
55
  let handoff = '';
@@ -70,6 +74,17 @@ function context(input: TrackAgentInput, repo: string): string {
70
74
  }
71
75
 
72
76
  export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps = {}): Promise<TrackAgentDecision> {
77
+ if (!(deps.shadow ?? getUserConfig().autopilot?.trackAgent?.shadow ?? false)) {
78
+ return decideTrackActionOnce(input, deps);
79
+ }
80
+ return decideShadowTrackAction(input, (value) => decideTrackActionOnce(value, deps), {
81
+ ...deps.shadowOptions,
82
+ maxDecisionsPerDay: deps.shadowOptions?.maxDecisionsPerDay
83
+ ?? getUserConfig().autopilot?.trackAgent?.shadowMaxDecisionsPerDay ?? 20,
84
+ });
85
+ }
86
+
87
+ async function decideTrackActionOnce(input: TrackAgentInput, deps: RunnerDeps): Promise<TrackAgentDecision> {
73
88
  try {
74
89
  const prompt = context(input, deps.root ?? repoDir());
75
90
  const persona = `track-${input.track}`;
@@ -83,7 +98,7 @@ export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps
83
98
  const values = decision as Record<string, unknown>;
84
99
  if (!['say', 'hold'].includes(String(values.action)) || (values.reason !== undefined && typeof values.reason !== 'string')
85
100
  || Object.keys(values).some((key) => !['action', 'reason'].includes(key))) throw new Error('invalid decision');
86
- if (typeof values.reason === 'string' && forbidden.test(values.reason)) throw new Error('forbidden action');
101
+ if (typeof values.reason === 'string' && forbiddenActionRegex.test(values.reason)) throw new Error('forbidden action');
87
102
  return decision as TrackAgentDecision;
88
103
  } catch (error) {
89
104
  log('decision-failed', { track: input.track, taskId: input.taskId, error: redactSecretText(String(error)) });
@@ -93,8 +108,8 @@ export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps
93
108
 
94
109
  export async function executeTrackAction(decision: TrackAgentDecision, input: TrackAgentInput, deps: RunnerDeps = {}): Promise<TrackAgentResult> {
95
110
  if (decision.action !== 'say') return { status: 'held', detail: decision.reason ?? '사람 확인' };
96
- if (forbidden.test(`${input.title}\n${input.prompt}`)) return { status: 'held', detail: '금지 작업 — 사람 확인' };
97
- if (!loadTrackRegistry().some(({ id }) => id === input.track)) return { status: 'held', detail: '트랙 미정 — 사람 확인' };
111
+ if (forbiddenActionRegex.test(`${input.title}\n${input.prompt}`)) return { status: 'held', detail: '금지 작업 — 사람 확인' };
112
+ if (!findTrack(input.track)) return { status: 'held', detail: '트랙 미정 — 사람 확인' };
98
113
  try {
99
114
  const checkBudget = deps.checkBudget ?? (async () => {
100
115
  const action = decideBudget(await readBudgetInputsLive()).action;
@@ -112,6 +127,11 @@ export async function executeTrackAction(decision: TrackAgentDecision, input: Tr
112
127
  running++;
113
128
  let held = false;
114
129
  try {
130
+ if (deps.shadow ?? getUserConfig().autopilot?.trackAgent?.shadow ?? false) {
131
+ const result = await launchTrackAgentShadow(input, deps.shadowOptions);
132
+ log('shadow-orchestrate', { track: input.track, taskId: input.taskId, status: result.status, detail: result.detail });
133
+ return result;
134
+ }
115
135
  const launch = deps.launch ?? (async (args: string[]) => {
116
136
  const { argv, env } = universeLaunch(args);
117
137
  const child = spawn('bun', argv, { cwd: repoDir(), env, stdio: 'ignore', detached: true });
@@ -6,7 +6,7 @@ import { join, resolve } from 'node:path';
6
6
  import { debug, redactSecretText } from '../debug/log.js';
7
7
  import { TaskStore } from '../task-orchestrator/store.js';
8
8
  import { getUserConfig, type UserConfig } from '../user-config.js';
9
- import { inferPhaseTrack, loadTrackRegistry } from './mission-phase-track.js';
9
+ import { inferPhaseTrack, loadTrackRegistry, trackPostIds } from './mission-phase-track.js';
10
10
  import type { TrackAgentInput, TrackAgentRunner } from './track-agent.js';
11
11
 
12
12
  type CourierConfig = NonNullable<NonNullable<UserConfig['autopilot']>['trackCourier']>;
@@ -66,7 +66,7 @@ export async function trackAlive(track: string, deps: CourierDeps = {}): Promise
66
66
  const comments = await (deps.listComments ?? (() => defaultComments(config.channelPr!, now, minutesFor(config))))();
67
67
  if (!Array.isArray(comments)) throw new Error('comments response is not an array');
68
68
  const timestamps = comments
69
- .filter((c) => c && typeof c.body === 'string' && c.body.startsWith(`**[${track}]**`))
69
+ .filter((c) => c && typeof c.body === 'string' && trackPostIds(track).some((id) => c.body.startsWith(`**[${id}]**`)))
70
70
  .map((c) => Date.parse(c.created_at))
71
71
  .filter((at) => Number.isFinite(at) && at <= now);
72
72
  if (!timestamps.length) return { alive: false, lastPostAt: null, reason: 'no-post' };
@@ -0,0 +1,16 @@
1
+ export function acpServerHelpText(): string {
2
+ return `
3
+ ACP server (Agent Client Protocol)
4
+ Usage: elanous --acp-server [--transport=stdio|unix-socket|websocket] [options]
5
+ Example: elanous --acp-server --transport=websocket
6
+
7
+ --transport=stdio|unix-socket|websocket Connection transport (default: stdio)
8
+ --socket-path=<path> Unix socket path (unix-socket only)
9
+ --port=<number> Listening port (websocket only)
10
+ --host=<host> Bind host (websocket only)
11
+ --tool-cwd=<path> Default working directory for tools
12
+ --no-auth Disable websocket token authentication
13
+
14
+ Zed setup and connection check: docs/manual/MANUAL-zed-acp.md
15
+ `;
16
+ }
@@ -1,3 +1,5 @@
1
+ import { classifyShortQuestion } from '../chat/short-question.js';
2
+ import { getUserConfig } from '../user-config.js';
1
3
  import { runCoreTurn } from '../core-turn/index.js';
2
4
  import type { LLMMessage, LLMToolSpec } from '../llm.js';
3
5
  import { maybeImageBearingResult } from '../llm.js';
@@ -83,6 +85,8 @@ export function summarizeToolResult(result: unknown): string | undefined {
83
85
 
84
86
  export async function runDaemonPromptTurn(opts: {
85
87
  history: DaemonSessionHistory;
88
+ /** B1 빠른 경로 스위치 — 없으면 config `chat.fastPath`(기본 true). 시험·호출부가 못 박을 때만 준다. */
89
+ fastPathEnabled?: boolean;
86
90
  request: DaemonPromptRequest;
87
91
  /** Optional multimodal prompt blocks for the current user turn.
88
92
  * When present, history persistence keeps the ACP block shape
@@ -173,6 +177,19 @@ export async function runDaemonPromptTurn(opts: {
173
177
  reason: surfaceResolutionReason,
174
178
  hasUserText: request.userText.length > 0,
175
179
  });
180
+ const hasAttachments = (opts.promptBlocks?.length ? opts.promptBlocks : request.userContent)
181
+ ?.some((block) => block.type !== 'text') ?? false;
182
+ // SW2 스위치 `chat.fastPath`(기본 켜짐 · PWA «짧은 물음은 빠르게» 카드) — 끄면 분류하지 않는다.
183
+ const fastPathEnabled = opts.fastPathEnabled ?? (() => { try { return getUserConfig().chat?.fastPath !== false; } catch { return true; } })();
184
+ const { fast, reason } = fastPathEnabled
185
+ ? classifyShortQuestion(request.userText, { hasAttachments })
186
+ : { fast: false, reason: 'disabled' };
187
+ const activeToolSurface = fast ? undefined : toolSurface;
188
+ const toolsBefore = toolSurface && toolSurface.kind !== 'none' ? toolSurface.specs.length : 0;
189
+ const tools: LLMToolSpec[] = activeToolSurface && activeToolSurface.kind !== 'none'
190
+ ? activeToolSurface.specs
191
+ : [];
192
+ debug.log('chat.fast-path', 'decided', { fast, reason, toolsBefore, toolsAfter: tools.length });
176
193
  // Image-pipeline followup #4 (2026-05-05) — when the webterm surface
177
194
  // is active, prepend a daemon-context block ("Current ACP sessionId:
178
195
  // …" + active terminals) to the system prompt so the LLM has the
@@ -181,21 +198,18 @@ export async function runDaemonPromptTurn(opts: {
181
198
  const augmentedSystemPrompt = appendWebtermContext(
182
199
  request.effectiveSystemPrompt,
183
200
  request.sessionId,
184
- toolSurface,
201
+ activeToolSurface,
185
202
  );
186
203
  const ctrl = new AbortController();
187
204
  if (opts.signal) {
188
205
  if (opts.signal.aborted) ctrl.abort();
189
206
  else opts.signal.addEventListener('abort', () => ctrl.abort(), { once: true });
190
207
  }
191
- const tools: LLMToolSpec[] = toolSurface && toolSurface.kind !== 'none'
192
- ? toolSurface.specs
193
- : [];
194
208
  const toolCwdResolver = createToolCwdResolver({
195
- tools: toolSurface?.kind ?? 'none',
209
+ tools: activeToolSurface?.kind ?? 'none',
196
210
  ...(opts.toolCwd !== undefined ? { toolCwd: opts.toolCwd } : {}),
197
211
  });
198
- const rawDispatchTool = toolSurface && toolSurface.kind !== 'none'
212
+ const rawDispatchTool = activeToolSurface && activeToolSurface.kind !== 'none'
199
213
  ? async (name: string, args: Record<string, unknown>, perCallCtx?: { callId: string }): Promise<unknown> => {
200
214
  const dispatchCtrl = new AbortController();
201
215
  const onAbort = (): void => dispatchCtrl.abort();
@@ -204,7 +218,7 @@ export async function runDaemonPromptTurn(opts: {
204
218
  // Image-pipeline followup #1 (2026-05-05) — pass the current
205
219
  // request.sessionId through so WebTerminal* tools can auto-
206
220
  // resolve scope when the LLM omits sessionId from args.
207
- return await toolSurface.dispatch(name, args, {
221
+ return await activeToolSurface.dispatch(name, args, {
208
222
  cwd: toolCwdResolver.cwd!,
209
223
  resolveWriteCwd: toolCwdResolver.resolveWriteCwd,
210
224
  signal: dispatchCtrl.signal,
@@ -246,9 +260,13 @@ export async function runDaemonPromptTurn(opts: {
246
260
  })
247
261
  : null;
248
262
  const dispatchTool = terminal ? terminal.dispatch : rawDispatchTool;
249
- const effectiveSystemPrompt = terminal
250
- ? terminal.systemPromptParts.join('\n\n')
251
- : augmentedSystemPrompt;
263
+ const fastPathGuidance = '확인이 필요하면 "확인해 볼까요?" 한 줄로 끝낸다';
264
+ const effectiveSystemPrompt = fast
265
+ ? [augmentedSystemPrompt, fastPathGuidance].filter(Boolean).join('\n')
266
+ : terminal
267
+ ? terminal.systemPromptParts.join('\n\n')
268
+ : augmentedSystemPrompt;
269
+ const isFirstTurn = history.get(request.sessionId).length === 0;
252
270
  const messages = opts.promptBlocks && opts.promptBlocks.length > 0
253
271
  ? appendUserPromptBlocksAndBuildMessages(
254
272
  history,
@@ -262,6 +280,9 @@ export async function runDaemonPromptTurn(opts: {
262
280
  request.userText,
263
281
  effectiveSystemPrompt,
264
282
  );
283
+ if (fast && !isFirstTurn) {
284
+ messages.unshift({ role: 'system', content: fastPathGuidance });
285
+ }
265
286
  const result = await runCoreTurn({
266
287
  sessionId: request.sessionId,
267
288
  userText: request.userText,
@@ -389,7 +389,13 @@ export function toolSurface(kind: DaemonToolSurfaceKind, cfg?: import('../../use
389
389
  }
390
390
  }
391
391
  if (shared.names.has(name)) {
392
- return shared.dispatch(name, args); // L2 core + L3 finance(gated) — buildSharedAppTools 단일 dispatch
392
+ // L2 core + L3 finance(gated) — buildSharedAppTools 단일 dispatch · B5: 진행 한 줄(emitFeedback)·세션·호출 id 를 넘긴다.
393
+ return shared.dispatch(name, args, {
394
+ surface: 'tui',
395
+ ...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
396
+ ...(ctx.toolCallId ? { toolCallId: ctx.toolCallId } : {}),
397
+ ...(ctx.emitFeedback ? { emitFeedback: ctx.emitFeedback } : {}),
398
+ });
393
399
  }
394
400
  if (name === 'Bash') {
395
401
  // PLAN-ios-rich-dev-feedback-hydrate M3 (2026-05-13) — pass
@@ -0,0 +1,84 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { codexCredentialRoot } from './codex-reset-credit-state.js';
4
+ import type { LlmPolicy } from '../policy/llm-policy.js';
5
+
6
+ interface CreditDay {
7
+ day: string;
8
+ accounts: Record<string, { dayOpen: number; balance: number }>;
9
+ }
10
+
11
+ export type CreditBalances = Readonly<Record<string, number>>;
12
+
13
+ function kstDay(now: Date): string {
14
+ return new Date(now.getTime() + 9 * 3_600_000).toISOString().slice(0, 10);
15
+ }
16
+
17
+ function dayNumber(day: string): number {
18
+ return Date.parse(`${day}T00:00:00Z`) / 86_400_000;
19
+ }
20
+
21
+ function creditDayPath(stateDir?: string): string {
22
+ return join(stateDir ?? codexCredentialRoot(), 'budget', 'codex-credit-day.json');
23
+ }
24
+
25
+ function readCreditDay(stateDir?: string): CreditDay | undefined {
26
+ try {
27
+ const parsed = JSON.parse(readFileSync(creditDayPath(stateDir), 'utf8')) as CreditDay;
28
+ return typeof parsed.day === 'string' && parsed.accounts && typeof parsed.accounts === 'object' && !Array.isArray(parsed.accounts)
29
+ ? parsed : undefined;
30
+ } catch { return undefined; }
31
+ }
32
+
33
+ /** Only a poller's observed balance can move the opening balance; readers never modify the ledger. */
34
+ export function recordCreditBalances(balances: CreditBalances, now: Date = new Date(), stateDir?: string): void {
35
+ const day = kstDay(now);
36
+ const previous = readCreditDay(stateDir);
37
+ const accounts: CreditDay['accounts'] = previous?.day === day ? { ...previous.accounts } : {};
38
+ for (const [name, balance] of Object.entries(balances)) {
39
+ if (!Number.isFinite(balance) || balance < 0) continue;
40
+ const prior = Object.prototype.hasOwnProperty.call(accounts, name) ? accounts[name] : undefined;
41
+ const dayOpen = prior && Number.isFinite(prior.dayOpen) && Number.isFinite(prior.balance)
42
+ ? (balance > prior.balance ? balance : prior.dayOpen)
43
+ : balance;
44
+ Object.defineProperty(accounts, name, { value: { dayOpen, balance }, enumerable: true, configurable: true, writable: true });
45
+ }
46
+ const path = creditDayPath(stateDir);
47
+ mkdirSync(dirname(path), { recursive: true });
48
+ const temporary = `${path}.${process.pid}.tmp`;
49
+ writeFileSync(temporary, `${JSON.stringify({ day, accounts }, null, 2)}\n`);
50
+ renameSync(temporary, path);
51
+ }
52
+
53
+ export function creditPaceStatus({ policy, balances, now = new Date(), stateDir }: {
54
+ policy: LlmPolicy;
55
+ balances: CreditBalances;
56
+ now?: Date;
57
+ stateDir?: string;
58
+ }): { active: boolean; todaySpent: number; target: number; remaining: number; daysLeft: number; projectedAtUntil: number } {
59
+ const day = kstDay(now);
60
+ const ledger = readCreditDay(stateDir);
61
+ const observed = Object.entries(balances).filter(([, balance]) => Number.isFinite(balance) && balance >= 0);
62
+ const todaySpent = ledger?.day === day ? observed.reduce((sum, [name, balance]) => {
63
+ const open = Object.prototype.hasOwnProperty.call(ledger.accounts, name) ? ledger.accounts[name]?.dayOpen : undefined;
64
+ return sum + (typeof open === 'number' && Number.isFinite(open) ? Math.max(0, open - balance) : 0);
65
+ }, 0) : 0;
66
+ const remaining = observed.reduce((sum, [, balance]) => sum + balance, 0);
67
+ const until = policy.credits.pace?.until;
68
+ const expiry = until ? Date.parse(`${until}T15:00:00Z`) : NaN;
69
+ const daysLeft = until ? Math.max(1, dayNumber(until) - dayNumber(day) + 1) : 1;
70
+ // The poller's opening observation anchors today's allocation. Spending cannot lower it;
71
+ // a new grant or the next KST day changes the opening observation.
72
+ const startingBalance = ledger?.day === day
73
+ ? observed.reduce((sum, [name, balance]) => {
74
+ const entry = Object.prototype.hasOwnProperty.call(ledger.accounts, name) ? ledger.accounts[name] : undefined;
75
+ return sum + (entry && Number.isFinite(entry.dayOpen) ? entry.dayOpen : balance);
76
+ }, 0)
77
+ : remaining;
78
+ const target = until ? Math.ceil(startingBalance / daysLeft) : 0;
79
+ return {
80
+ active: policy.credits.codex === 'use' && !!until && now.getTime() < expiry && remaining > 0 && todaySpent < target,
81
+ todaySpent, target, remaining, daysLeft,
82
+ projectedAtUntil: Math.max(0, remaining - Math.max(0, target - todaySpent) - target * (daysLeft - 1)),
83
+ };
84
+ }
@@ -0,0 +1,154 @@
1
+ // Codex prepaid-credit plan: days left until the credits expire, the daily spend that would use the balance up,
2
+ // and what was actually spent over the last 7 days. Disk only — the balance history is appended by
3
+ // writeQuotaSignal whenever a measured balance changes; nothing here touches the network.
4
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ const DAY_MS = 86_400_000;
8
+ const HISTORY_KEEP_MS = 30 * DAY_MS;
9
+ const HISTORY_FILE = 'codex-credit-history.jsonl';
10
+
11
+ export interface CreditHistoryEntry { at: string; home: string; balance: number }
12
+
13
+ export function creditHistoryPath(signalDir: string): string {
14
+ return join(signalDir, HISTORY_FILE);
15
+ }
16
+
17
+ export function readCreditHistory(signalDir: string): CreditHistoryEntry[] {
18
+ try {
19
+ const path = creditHistoryPath(signalDir);
20
+ if (!existsSync(path)) return [];
21
+ const out: CreditHistoryEntry[] = [];
22
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
23
+ if (!line.trim()) continue;
24
+ try {
25
+ const e = JSON.parse(line) as Partial<CreditHistoryEntry>;
26
+ if (typeof e.at === 'string' && typeof e.home === 'string' && typeof e.balance === 'number' && Number.isFinite(e.balance)) out.push(e as CreditHistoryEntry);
27
+ } catch { /* a torn line is skipped */ }
28
+ }
29
+ return out;
30
+ } catch { return []; }
31
+ }
32
+
33
+ /** Appends only when this home's balance changed; prunes entries older than 30 days. Fail-soft. */
34
+ export function appendCreditHistory(signalDir: string, home: string, balance: number, now: number = Date.now()): void {
35
+ try {
36
+ if (!Number.isFinite(balance)) return;
37
+ const history = readCreditHistory(signalDir);
38
+ const last = [...history].reverse().find((e) => e.home === home);
39
+ if (last && last.balance === balance) return;
40
+ mkdirSync(signalDir, { recursive: true });
41
+ const entry: CreditHistoryEntry = { at: new Date(now).toISOString(), home, balance };
42
+ const kept = history.filter((e) => now - Date.parse(e.at) <= HISTORY_KEEP_MS);
43
+ if (kept.length !== history.length) {
44
+ writeFileSync(creditHistoryPath(signalDir), [...kept, entry].map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf8');
45
+ } else {
46
+ appendFileSync(creditHistoryPath(signalDir), `${JSON.stringify(entry)}\n`, 'utf8');
47
+ }
48
+ } catch { /* observation never blocks the fetch */ }
49
+ }
50
+
51
+ /** Credits spent in the window: sum of balance drops per home (rises are top-ups and are not spend). */
52
+ export function creditSpend(history: readonly CreditHistoryEntry[], now: number, windowMs = 7 * DAY_MS): { spent: number; spanDays: number } | null {
53
+ const since = now - windowMs;
54
+ let spent = 0;
55
+ let earliest = Infinity;
56
+ const byHome = new Map<string, CreditHistoryEntry[]>();
57
+ for (const e of history) {
58
+ const at = Date.parse(e.at);
59
+ if (!Number.isFinite(at) || at > now) continue;
60
+ byHome.set(e.home, [...(byHome.get(e.home) ?? []), e]);
61
+ }
62
+ for (const entries of byHome.values()) {
63
+ entries.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
64
+ // The last entry before the window is the baseline for the first change inside it.
65
+ let start = entries.findIndex((e) => Date.parse(e.at) >= since);
66
+ if (start === -1) continue;
67
+ if (start > 0) start -= 1;
68
+ const used = entries.slice(start);
69
+ if (used.length < 2) continue;
70
+ earliest = Math.min(earliest, Math.max(since, Date.parse(used[0]!.at)));
71
+ for (let i = 1; i < used.length; i++) {
72
+ const drop = used[i - 1]!.balance - used[i]!.balance;
73
+ if (drop > 0) spent += drop;
74
+ }
75
+ }
76
+ if (!Number.isFinite(earliest)) return null;
77
+ return { spent, spanDays: Math.max((now - earliest) / DAY_MS, 1 / 24) };
78
+ }
79
+
80
+ export interface CodexCreditPlan {
81
+ /** policy credits.pace.until, else the earliest grant expiry; null when the policy records neither. */
82
+ expiresAt: string | null;
83
+ daysLeft: number | null;
84
+ /** Sum of the known per-account balances; null when no balance is known. */
85
+ totalBalance: number | null;
86
+ unknownBalances: number;
87
+ accounts: Array<{ name: string; balance: number | null; expires: string | null }>;
88
+ /** Credits per day that would use the balance up by the expiry date. */
89
+ dailyNeeded: number | null;
90
+ /** policy credits.pace.targetPerDay — the declared pace, shown beside the computed one. */
91
+ targetPerDay: number | null;
92
+ /** Credits actually spent per day over the last 7 days (or the shorter recorded span). */
93
+ actualPerDay: number | null;
94
+ actualSpanDays: number | null;
95
+ /** policy credits.pace.why */
96
+ note: string | null;
97
+ }
98
+
99
+ export interface CreditPolicyView {
100
+ codex: string;
101
+ grants: ReadonlyArray<{ account: string; amount: number; expires: string }>;
102
+ pace?: { targetPerDay: number; until: string; why?: string };
103
+ }
104
+
105
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
106
+
107
+ export function codexCreditPlan(input: {
108
+ now: number;
109
+ balances: ReadonlyArray<{ name: string; balance?: number }>;
110
+ credits: CreditPolicyView;
111
+ history: readonly CreditHistoryEntry[];
112
+ }): CodexCreditPlan {
113
+ const { credits } = input;
114
+ const accounts = input.balances.map((a) => {
115
+ const grantDates = credits.grants.filter((g) => g.account === a.name && DATE.test(g.expires)).map((g) => g.expires).sort();
116
+ return { name: a.name, balance: typeof a.balance === 'number' && Number.isFinite(a.balance) ? a.balance : null, expires: grantDates[0] ?? null };
117
+ });
118
+ const known = accounts.map((a) => a.balance).filter((b): b is number => b !== null);
119
+ const totalBalance = known.length ? known.reduce((x, y) => x + y, 0) : null;
120
+ const earliestGrant = credits.grants.map((g) => g.expires).filter((d) => DATE.test(d)).sort()[0];
121
+ const expiresAt = credits.pace?.until && DATE.test(credits.pace.until) ? credits.pace.until : earliestGrant ?? null;
122
+ const expiry = expiresAt ? Date.parse(`${expiresAt}T23:59:59+09:00`) : NaN;
123
+ const daysLeft = Number.isFinite(expiry) ? Math.max(0, Math.ceil((expiry - input.now) / DAY_MS)) : null;
124
+ const spend = creditSpend(input.history, input.now);
125
+ return {
126
+ expiresAt,
127
+ daysLeft,
128
+ totalBalance,
129
+ unknownBalances: accounts.length - known.length,
130
+ accounts,
131
+ dailyNeeded: daysLeft && totalBalance !== null ? totalBalance / daysLeft : null,
132
+ targetPerDay: credits.pace?.targetPerDay ?? null,
133
+ actualPerDay: spend ? spend.spent / Math.min(7, spend.spanDays) : null,
134
+ actualSpanDays: spend ? Math.round(Math.min(7, spend.spanDays) * 10) / 10 : null,
135
+ note: credits.pace?.why?.trim() || null,
136
+ };
137
+ }
138
+
139
+ /** The lines both surfaces print under the policy line. */
140
+ export function formatCodexCreditPlan(plan: CodexCreditPlan): string[] {
141
+ const n = (v: number) => String(Math.round(v));
142
+ const unknown = plan.unknownBalances ? ` (+${plan.unknownBalances}개 계정 모름)` : '';
143
+ const perAccount = plan.accounts.map((a) => `${a.name} ${a.balance === null ? '?' : n(a.balance)}${a.expires ? `(~${a.expires})` : ''}`).join(' · ');
144
+ const lines = [
145
+ `크레딧 잔액 합 ${plan.totalBalance === null ? '?' : n(plan.totalBalance)}${unknown} · 만료 ${plan.expiresAt ?? '모름 (policy credits.pace.until · credits.grants)'}${perAccount ? ` · ${perAccount}` : ''}`,
146
+ ];
147
+ const days = plan.daysLeft === null ? '만료일 모름' : `만료까지 ${plan.daysLeft}일`;
148
+ const needed = plan.dailyNeeded === null ? '?' : n(plan.dailyNeeded);
149
+ const target = plan.targetPerDay === null ? '' : ` (목표 ${n(plan.targetPerDay)})`;
150
+ const actual = plan.actualPerDay === null ? '기록 없음' : `${n(plan.actualPerDay)}/일${plan.actualSpanDays !== null && plan.actualSpanDays < 7 ? ` (기록 ${plan.actualSpanDays}일)` : ''}`;
151
+ lines.push(`계산 ${days} · 하루 소진 필요 ${needed}${target} · 최근 7일 실제 ${actual}`);
152
+ if (plan.note) lines.push(`메모 ${plan.note}`);
153
+ return lines;
154
+ }
@@ -9,6 +9,7 @@
9
9
  // ⇒ 직접 부르는 쪽은 «자기가» 감싸야 한다. 여기서 삼키면 저장 실패가 영영 안 보인다.
10
10
  // ⭐ 반면 `writeQuotaSignal` 은 «판정 신호»라 조회를 막으면 안 되므로 자기가 삼킨다(아래 주석).
11
11
 
12
+ import { appendCreditHistory } from './codex-credit-plan.js';
12
13
  import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
13
14
  import { createHash } from 'node:crypto';
14
15
  import { homedir } from 'node:os';
@@ -233,6 +234,7 @@ export function writeQuotaSignal(
233
234
  ...(typeof credits?.hasCredits === 'boolean' ? { hasCredits: credits.hasCredits } : {}),
234
235
  };
235
236
  writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
237
+ if (next.creditsBalance !== undefined && next.measuredHome) appendCreditHistory(dirname(path), next.measuredHome, next.creditsBalance);
236
238
  } catch { /* fail-soft */ }
237
239
  }
238
240
 
@@ -49,5 +49,8 @@ export function toDevChatOpts(opts: ChatCliOpts): DevChatOpts {
49
49
  export async function runChatCliCommand(text: string, opts: ChatCliOpts, deps: ChatCliDeps): Promise<void> {
50
50
  const spec = buildChatDevSpec(text, toDevChatOpts(opts));
51
51
  const run = deps.runDevPipeline ?? runDevPipeline;
52
- await run(spec, { runChatTurn: deps.runChatTurn });
52
+ await run(spec, {
53
+ runChatTurn: deps.runChatTurn,
54
+ ...(opts.json ? { progress: (message: string) => { process.stderr.write(`${message}\n`); } } : {}),
55
+ });
53
56
  }