elanous 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
  96. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/graphs/test-diet/recipes.yaml +9 -0
  178. package/graphs/test-diet/test-diet.yaml +22 -0
  179. package/package.json +6 -5
  180. package/persona-presets/_index.yaml +10 -0
  181. package/persona-presets/_schema.json +146 -0
  182. package/persona-presets/dana.yaml +26 -0
  183. package/persona-presets/haru.yaml +27 -0
  184. package/persona-presets/kai.yaml +27 -0
  185. package/persona-presets/leo.yaml +27 -0
  186. package/persona-presets/mira.yaml +30 -0
  187. package/persona-presets/nari.yaml +27 -0
  188. package/persona-presets/sora.yaml +27 -0
  189. package/persona-presets/yuna.yaml +27 -0
  190. package/plugins/card-followup/README.md +13 -4
  191. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  192. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  193. package/plugins/card-followup/graphs/run-step.ts +122 -18
  194. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  195. package/plugins/card-followup/plugin.json +1 -1
  196. package/plugins/doc-draft/README.md +13 -0
  197. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  198. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  199. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  200. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  201. package/plugins/doc-draft/plugin.json +24 -0
  202. package/plugins/geo-check/README.md +3 -1
  203. package/plugins/geo-check/graphs/run-step.ts +58 -13
  204. package/plugins/geo-check/plugin.json +1 -1
  205. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  206. package/plugins/job-coach/README.md +1 -1
  207. package/plugins/job-coach/graphs/run-step.ts +2 -2
  208. package/plugins/job-coach/plugin.json +1 -1
  209. package/plugins/job-coach/plugin.ts +1 -1
  210. package/scripts/affinity/endcard.sh +3 -2
  211. package/scripts/affinity/reframe.sh +3 -1
  212. package/scripts/ci-public-export-leak-gate.ts +48 -0
  213. package/scripts/codex-quota-alert.ts +122 -258
  214. package/scripts/coord-tracks.json +4 -4
  215. package/scripts/field-feed/deliver-node.ts +57 -0
  216. package/scripts/field-feed/feed-node.ts +67 -0
  217. package/scripts/field-feed/lib.ts +164 -0
  218. package/scripts/field-feed/reel-node.ts +24 -0
  219. package/scripts/field-feed/start.ts +62 -0
  220. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  221. package/scripts/install.sh +109 -21
  222. package/scripts/landing-heal/clean-checkout.ts +114 -0
  223. package/scripts/landing-heal/run-mission.ts +38 -3
  224. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  225. package/scripts/persona-presets/lint.ts +79 -0
  226. package/scripts/public-export.ts +8 -4
  227. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  228. package/scripts/release-loop/checklist-gate-node.ts +2 -2
  229. package/scripts/release-loop/env-known-failures.ts +31 -0
  230. package/scripts/release-loop/gate-node.ts +108 -22
  231. package/scripts/release-loop/known-issues-node.ts +159 -0
  232. package/scripts/release-loop/npm-publish-node.ts +124 -0
  233. package/scripts/release-loop/ops-upgrade-node.ts +95 -0
  234. package/scripts/release-loop/publish-node.ts +23 -1
  235. package/scripts/release-loop/release-readiness.ts +58 -0
  236. package/scripts/release-loop/shard-plan.ts +17 -0
  237. package/scripts/release-loop/unattended-release.ts +93 -0
  238. package/scripts/storyboard/storyboard.ts +167 -0
  239. package/scripts/test-diet/lib.ts +88 -0
  240. package/scripts/test-diet/node.ts +102 -0
  241. package/scripts/ux/measure-first-text.ts +102 -0
  242. package/scripts/vflow-collect.ts +20 -7
  243. package/scripts/vflow-search.ts +73 -0
  244. package/scripts/video-free-line-test-walker.ts +36 -0
  245. package/scripts/video-pipeline.ts +2 -2
  246. package/skills/explainer-video/SKILL.md +12 -9
  247. package/skills/explainer-video/engine/build.mjs +1 -1
  248. package/skills/explainer-video/engine/reel.mjs +138 -31
  249. package/skills/explainer-video/engine/reel.sh +2 -1
  250. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  251. package/skills/grill-me/SKILL.md +1 -1
  252. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  253. package/src/a2a/server.ts +2 -0
  254. package/src/acp/auth-methods.ts +30 -0
  255. package/src/acp/elanous-aware-peers.ts +30 -0
  256. package/src/acp/server.ts +96 -20
  257. package/src/agent/monad-agent-turn.ts +25 -6
  258. package/src/agent/shared-app-tools.ts +5 -3
  259. package/src/agent-substrate/review-intent.ts +6 -2
  260. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  261. package/src/auth/github-app-token.ts +4 -1
  262. package/src/autopilot/track-agent-shadow.ts +151 -0
  263. package/src/autopilot/track-agent.ts +23 -3
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/channel-import/detect.ts +191 -0
  271. package/src/channel-import/run.ts +111 -0
  272. package/src/chat/chat-cli.ts +4 -1
  273. package/src/chat/short-question.ts +40 -0
  274. package/src/cli/agent-cli.ts +24 -15
  275. package/src/cli/ask-bare.ts +42 -0
  276. package/src/cli/cli-user-error.ts +25 -0
  277. package/src/cli/codex-credit-plan-view.ts +25 -0
  278. package/src/cli/config-test-sync.ts +4 -0
  279. package/src/cli/decisions-cli.ts +13 -3
  280. package/src/cli/dependence-gauge.ts +237 -0
  281. package/src/cli/doctor-bundle.ts +128 -0
  282. package/src/cli/doctor-cli.ts +41 -1
  283. package/src/cli/doctor-local-llm.ts +95 -0
  284. package/src/cli/doctor-modes.ts +48 -0
  285. package/src/cli/import-cli.ts +60 -0
  286. package/src/cli/intake-cli.ts +12 -2
  287. package/src/cli/intake-source-cli.ts +58 -0
  288. package/src/cli/machine-cli.ts +23 -0
  289. package/src/cli/mcp-login.ts +13 -4
  290. package/src/cli/msg-cli.ts +137 -0
  291. package/src/cli/persona-cli.ts +116 -0
  292. package/src/cli/plugin-cli.ts +25 -0
  293. package/src/cli/pr-cli.ts +36 -0
  294. package/src/cli/provider-cli.ts +5 -0
  295. package/src/cli/release-cli.ts +50 -8
  296. package/src/cli/release-run-if-ready.ts +48 -0
  297. package/src/cli/setup-cli.ts +27 -6
  298. package/src/cli/setup-recommend.ts +8 -0
  299. package/src/cli/skills-cli.ts +173 -0
  300. package/src/cli/start-cli.ts +13 -4
  301. package/src/cli/usage-cli.ts +11 -1
  302. package/src/codex/setup.ts +37 -16
  303. package/src/dashboard/index.ts +18 -2
  304. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
  305. package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
  306. package/src/decisions/decision-cards.ts +291 -0
  307. package/src/decisions/decision-ledger.ts +5 -2
  308. package/src/decisions/discord-decision-cards.ts +133 -0
  309. package/src/decisions/telegram-decision-cards.ts +100 -0
  310. package/src/directives/version-at.ts +77 -0
  311. package/src/discord.ts +73 -6
  312. package/src/domains/core-tools.ts +37 -14
  313. package/src/domains/outbound-alert.ts +94 -12
  314. package/src/domains/repeated-failure.ts +1 -1
  315. package/src/error-report/report.ts +200 -0
  316. package/src/exec-requests/planner.ts +146 -0
  317. package/src/exec-requests/runner.ts +378 -0
  318. package/src/exec-requests/store.ts +78 -0
  319. package/src/field/field-feed-auto.ts +48 -0
  320. package/src/field/field-media.ts +8 -3
  321. package/src/field/field-reel.ts +203 -0
  322. package/src/grok/credential.ts +7 -5
  323. package/src/harness/aside-run-status.ts +33 -0
  324. package/src/harness/browser-verify.ts +48 -17
  325. package/src/harness/harness-cli-command.ts +25 -3
  326. package/src/harness/image-load-state.ts +21 -0
  327. package/src/harness/process-garbage.ts +45 -0
  328. package/src/hooks/error-report.ts +210 -0
  329. package/src/hooks/receiver.ts +41 -1
  330. package/src/import/apply.ts +128 -0
  331. package/src/import/plan.ts +197 -0
  332. package/src/index.ts +168 -21
  333. package/src/input-core/text-entry.ts +13 -5
  334. package/src/instance/leader.ts +13 -1
  335. package/src/intake-plane/collect-github.ts +5 -3
  336. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  337. package/src/intake-plane/discord-seat-work.ts +56 -0
  338. package/src/intake-plane/intake-sources.ts +202 -0
  339. package/src/intake-plane/items.ts +28 -12
  340. package/src/intake-plane/seat-answer.ts +96 -0
  341. package/src/intake-plane/seat-doc-route.ts +221 -0
  342. package/src/intake-plane/submit-intake-work.ts +9 -1
  343. package/src/intake-plane/telegram-seat-work.ts +72 -0
  344. package/src/llm/model-defaults.ts +24 -1
  345. package/src/llm.ts +5 -4
  346. package/src/machines/machine-ledger.ts +127 -0
  347. package/src/market/pack-internal-refs.ts +28 -0
  348. package/src/market/publish.ts +31 -4
  349. package/src/maturity/feature-maturity.ts +65 -0
  350. package/src/model-tier/turn-tier.ts +24 -0
  351. package/src/msg/msg-store.ts +149 -0
  352. package/src/native-tool-catalog.ts +2 -1
  353. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  354. package/src/nexus/api/consult-requests.ts +128 -0
  355. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  356. package/src/nexus/api/error-reports.ts +108 -0
  357. package/src/nexus/api/exec-requests.ts +51 -0
  358. package/src/nexus/api/field-uploads.ts +40 -3
  359. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  360. package/src/nexus/api/graph-approvals.ts +71 -4
  361. package/src/nexus/api/http-server.ts +82 -8
  362. package/src/nexus/api/outbound-report.ts +7 -0
  363. package/src/nexus/api/running-runs-background.ts +68 -0
  364. package/src/nexus/api/running-runs-child.ts +6 -0
  365. package/src/nexus/api/seat-requests.ts +254 -0
  366. package/src/nexus/api/share-captures.ts +91 -0
  367. package/src/nexus/api/skill-problems.ts +56 -0
  368. package/src/nexus/api/terminals.ts +24 -8
  369. package/src/nexus/api/trace-run-scan.ts +37 -0
  370. package/src/nexus/api/trace.ts +19 -3
  371. package/src/nexus/index.ts +28 -1
  372. package/src/nexus/setup-status.ts +11 -7
  373. package/src/notify/llm-budget-alert.ts +16 -10
  374. package/src/oauth/browser-availability.ts +14 -0
  375. package/src/oauth/codex-account-rotation.ts +26 -4
  376. package/src/oauth/codex-account-store.ts +20 -2
  377. package/src/oauth/codex-browser-login.ts +173 -0
  378. package/src/oauth/codex-quota-policy.ts +16 -0
  379. package/src/oauth/codex.ts +4 -1
  380. package/src/oauth/fallback-chain.ts +5 -5
  381. package/src/oauth/store.ts +42 -5
  382. package/src/onboarding/continue-here.ts +34 -0
  383. package/src/onboarding/entry-hints.ts +8 -0
  384. package/src/onboarding/full-screen-io.ts +35 -3
  385. package/src/onboarding/help/skills.md +1 -1
  386. package/src/onboarding/non-interactive.ts +1 -0
  387. package/src/onboarding/subscription-hint.ts +27 -0
  388. package/src/onboarding/web-first.ts +90 -0
  389. package/src/onboarding.ts +287 -26
  390. package/src/persona/presets.ts +105 -0
  391. package/src/plugins/maker/node-install.ts +92 -0
  392. package/src/plugins/maker/node-maker.ts +139 -0
  393. package/src/plugins/maker/plugin-maker.ts +19 -7
  394. package/src/policy/llm-policy.ts +268 -0
  395. package/src/prompt-library/session-guidance.ts +7 -1
  396. package/src/prompt-library/universal-preamble.ts +2 -2
  397. package/src/relay/relay-cli.ts +20 -0
  398. package/src/relay/relay-server.ts +220 -0
  399. package/src/release-loop/checklist.ts +37 -13
  400. package/src/self-dev/decompose.ts +1 -0
  401. package/src/self-dev/dev-pipeline.ts +6 -3
  402. package/src/self-dev/orchestrate.ts +18 -11
  403. package/src/self-dev/run-record-mirror.ts +44 -0
  404. package/src/self-dev/run-store.ts +12 -3
  405. package/src/self-implement/harness-policy-gates.ts +4 -1
  406. package/src/self-implement/report-origin.ts +12 -1
  407. package/src/session/chat.ts +17 -5
  408. package/src/shell-env-bootstrap.ts +44 -7
  409. package/src/skills/connect-claude-plugin.ts +142 -0
  410. package/src/skills/connect.ts +70 -0
  411. package/src/skills/index.ts +29 -1
  412. package/src/skills/repair.ts +124 -0
  413. package/src/skills/runner.ts +11 -2
  414. package/src/skills/shared-agent-skill-roots.ts +25 -0
  415. package/src/skills/trigger-extract.ts +1 -1
  416. package/src/steward/failure-streak.ts +78 -0
  417. package/src/steward/steward-cards.ts +24 -0
  418. package/src/steward/triage-budget.ts +127 -0
  419. package/src/steward/triage-plan.ts +75 -0
  420. package/src/steward/triage.ts +244 -42
  421. package/src/task-cards/card-store.ts +1 -1
  422. package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  424. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  426. package/src/telegram-commands.ts +13 -0
  427. package/src/telegram.ts +72 -2
  428. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  429. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  430. package/src/user-config.ts +189 -15
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/video-pipeline/recipes/character.ts +42 -5
  433. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  434. package/src/video-pipeline/recipes/free-line.ts +16 -3
  435. package/src/video-pipeline/recipes/remote.ts +7 -1
  436. package/src/video-pipeline/vflow-reference.ts +109 -0
  437. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  446. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  515. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  516. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  517. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
@@ -0,0 +1,111 @@
1
+ // EN13 — 감지 → 미리보기 → 동의 → 시크릿 저장 → 연결 확인(읽기 1회). 토큰 값은 화면·로그·채널 어디에도 안 나간다.
2
+ import { homedir } from 'node:os';
3
+ import { debug } from '../debug/log.js';
4
+ import { detectChannelImports, pickPerPlatform, previewLine, SOURCE_LABEL, type ImportCandidate, type ImportPlatform } from './detect.js';
5
+
6
+ export interface ChannelImportDeps {
7
+ home?: string;
8
+ out?: { log: (s: string) => void; error: (s: string) => void };
9
+ /** Asks once; returns true for yes. Absent or non-interactive → no. */
10
+ ask?: (question: string) => Promise<boolean>;
11
+ yes?: boolean;
12
+ /** Current token for that platform (to say «already the same»), or null. */
13
+ currentToken?: (platform: ImportPlatform) => string | null | Promise<string | null>;
14
+ store?: (platform: ImportPlatform, token: string, allowedUsers: string[] | undefined) => Promise<void>;
15
+ probe?: (platform: ImportPlatform, token: string) => Promise<{ ok: boolean; botName?: string }>;
16
+ }
17
+
18
+ export interface ChannelImportOutcome {
19
+ exitCode: number;
20
+ imported: { platform: ImportPlatform; source: string; connected: boolean; botName?: string }[];
21
+ }
22
+
23
+ const NAME: Record<ImportPlatform, string> = { telegram: '텔레그램', discord: '디스코드' };
24
+
25
+ async function defaultStore(platform: ImportPlatform, token: string, allowedUsers: string[] | undefined): Promise<void> {
26
+ const { storeChannelBotToken } = await import('../channel-bot-token.js');
27
+ await storeChannelBotToken(platform, token, allowedUsers);
28
+ }
29
+
30
+ async function defaultProbe(platform: ImportPlatform, token: string): Promise<{ ok: boolean; botName?: string }> {
31
+ const { probeChannelBotToken } = await import('../channel-bot-token.js');
32
+ return probeChannelBotToken(platform, token);
33
+ }
34
+
35
+ async function defaultCurrentToken(platform: ImportPlatform): Promise<string | null> {
36
+ try {
37
+ const { resolveChannelBotToken } = await import('../channel-bot-token.js');
38
+ const { getUserConfig } = await import('../user-config.js');
39
+ return resolveChannelBotToken(platform, getUserConfig())?.token ?? null;
40
+ } catch { return null; }
41
+ }
42
+
43
+ export async function runChannelImport(deps: ChannelImportDeps = {}): Promise<ChannelImportOutcome> {
44
+ const out = deps.out ?? console;
45
+ const home = deps.home ?? homedir();
46
+ const { candidates, unreadable } = detectChannelImports(home);
47
+ for (const u of unreadable) out.log(`· ${u.file}: ${u.reason} — 건너뜁니다`);
48
+ const { chosen, others } = pickPerPlatform(candidates);
49
+ debug.log('channel-import', 'detected', {
50
+ sources: candidates.map((c) => `${c.source}:${c.platform}`), chosen: chosen.length, others: others.length, unreadable: unreadable.length,
51
+ });
52
+ if (!chosen.length) {
53
+ out.log('가져올 텔레그램·디스코드 설정을 찾지 못했습니다(OpenClaw · Hermes · 이전 엘라누스). 직접 넣으려면: elanous nexus channel-bot setup telegram');
54
+ return { exitCode: 0, imported: [] };
55
+ }
56
+
57
+ const current = deps.currentToken ?? defaultCurrentToken;
58
+ const todo: ImportCandidate[] = [];
59
+ out.log('이미 쓰던 채널 설정을 찾았습니다:');
60
+ for (const c of chosen) {
61
+ const existing = await current(c.platform);
62
+ if (existing && existing === c.token.reveal()) { out.log(`· ${previewLine(c)} — 이미 같은 토큰을 쓰고 있습니다`); continue; }
63
+ out.log(`· ${previewLine(c)}${existing ? ' — ⚠️ 지금 쓰는 토큰을 바꿉니다' : ''}`);
64
+ todo.push(c);
65
+ }
66
+ for (const c of others) out.log(` (${NAME[c.platform]} 토큰이 ${SOURCE_LABEL[c.source]}에도 있습니다 — 다른 토큰이라 위의 것만 가져옵니다)`);
67
+ if (!todo.length) return { exitCode: 0, imported: [] };
68
+
69
+ const question = `가져올까요? 토큰은 엘라누스 비밀 저장소에만 넣고 화면·로그에는 남기지 않습니다. [Y/n] `;
70
+ const agreed = deps.yes || (deps.ask ? await deps.ask(question) : false);
71
+ if (!agreed) {
72
+ out.log(deps.ask || deps.yes ? '가져오지 않았습니다.' : '가져오려면: elanous nexus channel-bot import --yes');
73
+ debug.log('channel-import', 'declined', { count: todo.length });
74
+ return { exitCode: 0, imported: [] };
75
+ }
76
+
77
+ const store = deps.store ?? defaultStore;
78
+ const probe = deps.probe ?? defaultProbe;
79
+ const imported: ChannelImportOutcome['imported'] = [];
80
+ let failed = 0;
81
+ for (const c of todo) {
82
+ try {
83
+ await store(c.platform, c.token.reveal(), c.allowedUsers.length ? c.allowedUsers : undefined);
84
+ } catch (error) {
85
+ failed++;
86
+ // Store errors are our own (lock, invalid ids) — print the class only, never the message, in case it carries input.
87
+ const reason = error instanceof Error && error.message === 'invalid-allowed-users' ? '허용 사용자 형식이 맞지 않습니다' : '저장하지 못했습니다';
88
+ out.error(`✗ ${NAME[c.platform]}: ${reason}`);
89
+ debug.log('channel-import', 'store-failed', { platform: c.platform, source: c.source }, { level: 'warn' });
90
+ continue;
91
+ }
92
+ const check = await probe(c.platform, c.token.reveal()).catch(() => ({ ok: false } as { ok: boolean; botName?: string }));
93
+ imported.push({ platform: c.platform, source: c.source, connected: check.ok, ...(check.botName ? { botName: check.botName } : {}) });
94
+ debug.log('channel-import', 'stored', { platform: c.platform, source: c.source, connected: check.ok });
95
+ out.log(check.ok
96
+ ? `✓ ${NAME[c.platform]}: 저장했고 연결을 확인했습니다 — @${check.botName}`
97
+ : `△ ${NAME[c.platform]}: 저장했지만 연결 확인에 실패했습니다 — 토큰이 만료됐거나 네트워크 문제일 수 있습니다(다시 넣기: elanous nexus channel-bot setup ${c.platform})`);
98
+ }
99
+ if (imported.length) out.log('봇은 다음 넥서스 시작 때 켜집니다.');
100
+ return { exitCode: failed ? 1 : 0, imported };
101
+ }
102
+
103
+ /** One line for onboarding — null when there is nothing to import. Never reveals a value. */
104
+ export function channelImportHint(home = homedir()): string | null {
105
+ try {
106
+ const { chosen } = pickPerPlatform(detectChannelImports(home).candidates);
107
+ if (!chosen.length) return null;
108
+ const what = chosen.map((c) => `${NAME[c.platform]}(${SOURCE_LABEL[c.source]})`).join(' · ');
109
+ return `쓰던 채널 설정 찾음: ${what} — 가져오려면: elanous nexus channel-bot import`;
110
+ } catch { return null; }
111
+ }
@@ -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
  }
@@ -0,0 +1,40 @@
1
+ // B1(UX · 0.2.7) — 짧은 «물음»은 도구 없이 빠른 경로로(첫 글자 ≤2초 · 내부 문서 `EVAL-bot-chat-vs-grok-2026-09-30`).
2
+ // 허용 방식(골 ASK-bot-short-question-fast-path-b1b): 아래가 «모두» 참일 때만 fast — 하나라도 거짓이면 fast 아님(보수 쪽).
3
+ // ⓐ 한 줄 · 80자 이하 · 첨부 없음 ⓑ 인사이거나 «물음 꼴» ⓒ 요청·작업 표지 · 경로·URL·코드 꼴 · 실시간 낱말이 없다.
4
+ // 거부 목록으로 변형을 다 막을 수는 없다 — 오분류 비용은 «확인해 볼까요?» 한 줄로 막힌다(도구가 안 실릴 뿐 대화는 이어진다).
5
+ // 판정 표본 = src/chat/short-question.test.ts 의 고정 표 20개(⊕ 리뷰가 든 변형).
6
+
7
+ export interface ShortQuestionContext {
8
+ hasAttachments?: boolean;
9
+ }
10
+ export interface ShortQuestionVerdict {
11
+ fast: boolean;
12
+ reason: string;
13
+ }
14
+
15
+ const GREETING = /^(?:안녕(?:하세요|하십니까)?|반가워(?:요)?|고마워(?:요)?|감사합니다|하이|hi|hello|hey|thanks|thank you)[!?.~\s]*$/i;
16
+ // «한 줄로» 같은 짧은 꼬리를 떼고 본다.
17
+ const TAIL = /\s*(?:한\s*줄로|짧게|간단히|briefly|in one line)[.!]?\s*$/i;
18
+ const KO_QUESTION_END = /(?:\?|까|니|나요|냐|야|어때|뭐|몇|언제|어디|누구|왜|인가|일까)[?.!~]*$/;
19
+ const EN_QUESTION = /^(?:what|why|how|who|where|when|which|is|are|does|do)\b.*\?$/i;
20
+ const REQUEST_KO = /(?:해\s*줘|해\s*주세요|줘(?:요)?(?=[?.!\s]|$)|주세요|줄래|할래|볼래|줄\s*수\s*있|돌려|실행|고쳐|만들어\s*줘|찾아|검색|확인해|열어|보내|올려|지워|써\s*줘|작성해|요약해|정리해|분석해)/;
21
+ const REQUEST_EN = /\b(?:please|can you|could you|would you|run|test|tests|fix|build|open|search|check|send|create|write|deploy|install|summarize)\b/i;
22
+ const TASK = /(?:작업|파일|폴더|디렉터리|레포|저장소|커밋|브랜치|\bPR\b|코드|스크립트|테스트|빌드|배포|설치|설정|명령어|로그|버그|이슈)/i;
23
+ const PATH_OR_CODE = /(?:https?:\/\/|www\.|[\w.-]+\/[\w./-]+|\.(?:ts|tsx|js|py|md|json|yaml|swift|kt)\b|[`{};=<>])/i;
24
+ const LIVE = /(?:오늘\s*(?:날짜|며칠|무슨\s*요일)|지금\s*몇\s*시|최신|뉴스|속보|주가|시세|환율|날씨|today'?s|latest|news|stock|weather|right now)/i;
25
+
26
+ export function classifyShortQuestion(text: string, ctx: ShortQuestionContext = {}): ShortQuestionVerdict {
27
+ const q = text.trim();
28
+ if (!q) return { fast: false, reason: 'empty' };
29
+ if (ctx.hasAttachments) return { fast: false, reason: 'attachment' };
30
+ if (/[\r\n\u2028\u2029]/.test(q)) return { fast: false, reason: 'multiline' };
31
+ if ([...q].length > 80) return { fast: false, reason: 'long' };
32
+ if (PATH_OR_CODE.test(q)) return { fast: false, reason: 'path-or-code' };
33
+ if (LIVE.test(q)) return { fast: false, reason: 'live' };
34
+ if (REQUEST_KO.test(q) || REQUEST_EN.test(q)) return { fast: false, reason: 'request' };
35
+ if (TASK.test(q)) return { fast: false, reason: 'task' };
36
+ if (GREETING.test(q)) return { fast: true, reason: 'greeting' };
37
+ const core = q.replace(TAIL, '').trim();
38
+ if (KO_QUESTION_END.test(core) || EN_QUESTION.test(core)) return { fast: true, reason: 'question' };
39
+ return { fast: false, reason: 'not-a-question' };
40
+ }
@@ -393,8 +393,8 @@ tierCmd
393
393
  * When `json` is true, the function suppresses the streaming text +
394
394
  * ui.info trailer and instead emits exactly ONE JSON line on stdout
395
395
  * at end-of-turn:
396
- * {sessionId, provider, model, reply, logPath, budget, durationMs,
397
- * turnIndex, ts}
396
+ * {sessionId, provider, model, reply, finalReply, transcript, logPath,
397
+ * budget, durationMs, ts}
398
398
  * Stable shape so LLMs can self-spawn `elanous chat` for follow-ups
399
399
  * without parsing human-readable terminal output. */
400
400
  export async function runChatTurnCli(opts: {
@@ -442,12 +442,14 @@ export async function runChatTurnCli(opts: {
442
442
  dbg.setAmbientSessionId?.(session.id);
443
443
  } catch { /* debug module unavailable — fine */ }
444
444
  const replyChunks: string[] = [];
445
- // 어시스턴트 메시지 경계(도구 호출)로 자른 본문 조각 — 마지막 «비어 있지 않은» 조각이 finalReply.
445
+ // Tool calls delimit assistant messages. finalReply retains its last non-empty-message meaning.
446
446
  let segmentChunks: string[] = [];
447
447
  let lastSegment = '';
448
+ let finalMessage = '';
449
+ let sawToolCall = false;
448
450
  const closeSegment = (): void => {
449
- const text = segmentChunks.join('').trim();
450
- if (text) lastSegment = text;
451
+ finalMessage = segmentChunks.join('').trim();
452
+ if (finalMessage) lastSegment = finalMessage;
451
453
  segmentChunks = [];
452
454
  };
453
455
  if (!opts.json) {
@@ -528,15 +530,17 @@ export async function runChatTurnCli(opts: {
528
530
  userText: opts.userText,
529
531
  systemPrompt: agentSystemPrompt,
530
532
  onDelta: (d) => {
531
- if (opts.json) { replyChunks.push(d); segmentChunks.push(d); }
532
- else process.stdout.write(d);
533
+ replyChunks.push(d);
534
+ segmentChunks.push(d);
535
+ if (!opts.json && !opts.enableTools) process.stdout.write(d);
533
536
  },
534
537
  tools: cliToolSpecs,
535
538
  dispatchTool,
536
539
  ...(opts.goalLoop ? { goalLoop: true } : {}),
537
540
  onToolCall: (call) => {
538
- // 도구 호출 뒤의 본문은 «새» 어시스턴트 메시지다 — 직전 조각을 마감한다(finalReply 용).
539
- if (opts.json) closeSegment();
541
+ // The next delta belongs to the next assistant message.
542
+ sawToolCall = true;
543
+ closeSegment();
540
544
  if (!opts.json) {
541
545
  process.stdout.write(`\n ⏺ ${call.name}(${truncateArgsForLog(call.args)})\n`);
542
546
  }
@@ -555,6 +559,12 @@ export async function runChatTurnCli(opts: {
555
559
  }
556
560
  throw err;
557
561
  }
562
+ if (!sawToolCall && replyChunks.length === 0 && result.text) {
563
+ replyChunks.push(result.text);
564
+ segmentChunks.push(result.text);
565
+ if (!opts.json && !opts.enableTools) process.stdout.write(result.text);
566
+ }
567
+ closeSegment();
558
568
  if (opts.json) {
559
569
  let logPath: string | null = null;
560
570
  try {
@@ -566,11 +576,9 @@ export async function runChatTurnCli(opts: {
566
576
  sessionId: session.id,
567
577
  provider: result.provider,
568
578
  model: result.model ?? null,
569
- reply: replyChunks.join(''),
570
- // ⭐ 2026-09-23 — `reply` 는 «모든 턴·모든 목표 루프 반복»의 본문을 이어 붙인다(뜻은 그대로 둔다 · 소비자 보호).
571
- // 그래서 모델이 반복마다 최종 답을 다시 말하면 같은 문장이 여러 번 나온다(실측: kimi 3회·grok 2회).
572
- // `finalReply` = 마지막 어시스턴트 메시지 본문 — 「최종 답만」이 필요한 소비자용(뒤호환 추가 칸).
573
- finalReply: (closeSegment(), lastSegment),
579
+ reply: finalMessage,
580
+ transcript: replyChunks.join(''),
581
+ finalReply: lastSegment,
574
582
  budget: sessionBudget(session.id),
575
583
  durationMs: Date.now() - startedAt,
576
584
  logPath,
@@ -579,7 +587,8 @@ export async function runChatTurnCli(opts: {
579
587
  await writeStdoutJson(JSON.stringify(out) + '\n');
580
588
  return;
581
589
  }
582
- process.stdout.write('\n');
590
+ if (opts.enableTools) process.stdout.write(`${finalMessage}\n`);
591
+ else process.stdout.write('\n');
583
592
  ui.info(`[session ${session.id.slice(0, 8)} ${result.provider}${result.model ? '/' + result.model : ''} ${sessionBudget(session.id)}]`);
584
593
  }
585
594
 
@@ -0,0 +1,42 @@
1
+ import { decideProviderForConfig, getProviderForConfig, streamLLM, type LLMMessage } from '../llm.js';
2
+ import { getUserConfig } from '../user-config.js';
3
+ import { DEFAULT_TOKEN_BUDGET, trimToBudget } from '../tokens.js';
4
+
5
+ export interface AskBareDeps {
6
+ getUserConfig: typeof getUserConfig;
7
+ decideProviderForConfig: typeof decideProviderForConfig;
8
+ getProviderForConfig: typeof getProviderForConfig;
9
+ streamLLM: typeof streamLLM;
10
+ trimToBudget: typeof trimToBudget;
11
+ }
12
+
13
+ export async function askBare({ text, provider, model, deps }: {
14
+ text: string;
15
+ provider?: string;
16
+ model?: string;
17
+ deps: AskBareDeps;
18
+ }): Promise<{ provider: string; model: string; reply: string }> {
19
+ const messages: LLMMessage[] = [{ role: 'user', content: text }];
20
+ if (deps.trimToBudget(messages, DEFAULT_TOKEN_BUDGET).used > DEFAULT_TOKEN_BUDGET) {
21
+ throw new Error(`ask --bare: input exceeds the ${DEFAULT_TOKEN_BUDGET}-token context budget`);
22
+ }
23
+ const cfg = deps.getUserConfig();
24
+ const selectedConfig = provider === undefined ? cfg : {
25
+ ...cfg,
26
+ llm: {
27
+ ...cfg.llm,
28
+ provider: provider as typeof cfg.llm.provider,
29
+ model: model ?? (provider === cfg.llm.provider ? cfg.llm.model : undefined),
30
+ },
31
+ };
32
+ const selected = deps.decideProviderForConfig(selectedConfig, model);
33
+ const chosenProvider = deps.getProviderForConfig(selectedConfig, model);
34
+ let resolvedProvider = chosenProvider.name;
35
+ let resolvedModel = selected.model;
36
+ const reply = await deps.streamLLM(messages, () => {}, {
37
+ ...(provider === undefined ? { initialProvider: chosenProvider } : { provider: chosenProvider }),
38
+ ...(selected.model === '(none)' ? {} : { model: selected.model }),
39
+ onResolvedProvider: (name, activeModel) => { resolvedProvider = name; resolvedModel = activeModel; },
40
+ });
41
+ return { provider: resolvedProvider, model: resolvedModel, reply };
42
+ }
@@ -0,0 +1,25 @@
1
+ import { HarnessCliInputError } from '../harness/harness-cli-command.js';
2
+
3
+ const cliUserErrorBrand = Symbol.for('elanous.cli.CliUserError');
4
+ const harnessCliInputErrorBrand = Symbol.for('elanous.cli.HarnessCliInputError');
5
+
6
+ export class CliUserError extends Error {
7
+ constructor(message: string, public readonly hint?: string) {
8
+ super(message);
9
+ this.name = 'CliUserError';
10
+ Object.defineProperty(this, cliUserErrorBrand, { value: true });
11
+ }
12
+ }
13
+
14
+ export function isCliUserError(err: unknown): err is Error & { hint?: string } {
15
+ return err instanceof HarnessCliInputError || err instanceof CliUserError || (
16
+ err instanceof Error && (
17
+ (err.name === 'CliUserError' && Object.getOwnPropertyDescriptor(err, cliUserErrorBrand)?.value === true) ||
18
+ (err.name === 'HarnessCliInputError' && Object.getOwnPropertyDescriptor(err, harnessCliInputErrorBrand)?.value === true)
19
+ )
20
+ );
21
+ }
22
+
23
+ export function formatCliUserError(err: Error & { hint?: string }): string {
24
+ return `❌ ${err.message}${err.hint ? `\n ↳ ${err.hint}` : ''}`;
25
+ }
@@ -0,0 +1,25 @@
1
+ // Assembles the codex credit plan from disk for `provider codex status` and `usage` — policy file, the last
2
+ // measured per-account balances and the balance history. No network.
3
+ import { codexCreditPlan, readCreditHistory, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
4
+ import { quotaSignalDir } from '../budget/codex-reset-credit-state.js';
5
+ import { inspectCodexRotation } from '../oauth/codex-account-store.js';
6
+ import { loadLlmPolicy } from '../policy/llm-policy.js';
7
+
8
+ export function codexCreditPlanFromDisk(now: number = Date.now()): CodexCreditPlan {
9
+ const s = inspectCodexRotation(process.env, { now });
10
+ const seen = new Set<string>();
11
+ const balances: Array<{ name: string; balance?: number }> = [];
12
+ const push = (key: string, name: string, balance: number | undefined) => {
13
+ if (seen.has(key)) return;
14
+ seen.add(key);
15
+ balances.push({ name, ...(balance === undefined ? {} : { balance }) });
16
+ };
17
+ push(s.current.storeKey, s.current.name, s.currentCreditBalance);
18
+ for (const c of s.candidates) push(c.storeKey, c.name, c.creditBalance);
19
+ return codexCreditPlan({
20
+ now,
21
+ balances,
22
+ credits: loadLlmPolicy({ now: new Date(now) }).policy.credits,
23
+ history: readCreditHistory(quotaSignalDir()),
24
+ });
25
+ }
@@ -41,6 +41,9 @@ export const TEST_SYNC_AUX_FILES: readonly string[] = [
41
41
  'llm-fallback.json', // 모델 폴백 체인
42
42
  'auth.json', // oauth 자격 (LLM provider)
43
43
  'identity.json', // self identity — 무해·자기인식 일관
44
+ // POL1 (10-01): the LLM policy (credit use · pace · grants). Without it a tree's test universe resolved «fallback»
45
+ // while production said «credits», and every harness run from a work tree went to grok.
46
+ 'policy/llm.yaml',
44
47
  ];
45
48
 
46
49
  /** 의도적 제외 (복사 금지 — 이유 명시. 목록 변경 시 반드시 사유와 함께). */
@@ -201,6 +204,7 @@ export function syncTestConfig(testDir: string, srcDir: string = prodConfigDir()
201
204
  // 목록 뒤의 `auth.json`(codex 로그인)이 복사되지 않아 격리 우주의 자식이 codex 를 못 썼다.
202
205
  // ⇒ 이미 같은 바이트면 건너뛴다(멱등) — 다르면 종전처럼 복사한다(실패는 그대로 드러난다).
203
206
  if (existsSync(to) && readFileSync(from).equals(readFileSync(to))) { chmodSync(to, 0o600); skippedIdentical.push(f); continue; }
207
+ mkdirSync(dirname(to), { recursive: true });
204
208
  copyFileSync(from, to);
205
209
  chmodSync(to, 0o600);
206
210
  copied.push(f);
@@ -9,6 +9,12 @@ function date(raw?: string): string | undefined {
9
9
  if (!Number.isFinite(parsed.getTime())) throw new Error(`invalid since: ${raw}`);
10
10
  return parsed.toISOString();
11
11
  }
12
+ /** `+6h` → now + 6 hours; otherwise a UTC ISO timestamp (validated by the ledger). */
13
+ function dueAt(raw: string): string {
14
+ const hours = /^\+(\d+(?:\.\d+)?)h$/.exec(raw.trim());
15
+ if (hours) return new Date(Date.now() + Number(hours[1]) * 3600000).toISOString();
16
+ return raw.trim();
17
+ }
12
18
  const kst = (at: string) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium' }).format(new Date(at)) + ' KST';
13
19
  const version = (e: DecisionEntry['version']) => e ? `released=${e.released ?? '-'} · dev=${e.dev ?? '-'}${e.codename ? ` «${e.codename}»` : ''}` : '판 미상';
14
20
  function agent(o?: string): string {
@@ -30,6 +36,7 @@ export function formatDecisionDetail(e: DecisionEntry): string {
30
36
  '선택지 | 결과', ...(e.options.length ? e.options.map(o => `${o.key}: ${o.label} | ${o.consequence}`) : ['원문에 선택지 없음 · 확인 필요']),
31
37
  'skipped' in e.recommendation ? `권고 생략: ${e.recommendation.reason}` : `권고: ${e.recommendation.option} — ${e.recommendation.why}`,
32
38
  `올림: ${e.raisedAt ? kst(e.raisedAt) : '시각 미상'} · ${e.raisedBy.agent} · ${version(e.version)}`,
39
+ ...(e.dueAt ? [`기한: ${kst(e.dueAt)}`] : []),
33
40
  ...(e.importedAt ? [`가져옴: ${kst(e.importedAt)}`] : []),
34
41
  ...(e.status === 'decided' ? [`결정: ${e.decidedAt ? kst(e.decidedAt) : '시각 미상'} · ${e.decidedBy?.kind === 'auto' ? `AUTO ${e.decidedBy.agent} (${e.decidedBy.delegation})` : e.decidedBy?.kind === 'human' ? 'human' : '주체 미상'} · ${e.choice ?? '선택 키 미상'} · ${e.versionAtDecision ? version(e.versionAtDecision) : '결정 당시 판 미상'}`,
35
42
  ...(e.note ? [`메모: ${e.note}`] : [])] : []),
@@ -48,14 +55,17 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
48
55
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
49
56
  .option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
50
57
  .option('--track <track>').option('--agent <agent>').option('--session <id>')
51
- .option('--ref <url>', '참조 (반복)', repeat, [] as string[]).option('--json')
52
- .action((o: { title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
58
+ .option('--ref <url>', '참조 (반복)', repeat, [] as string[])
59
+ .option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
60
+ .option('--json')
61
+ .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
53
62
  if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
54
63
  if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
55
64
  const options: DecisionOption[] = o.option.map(parseOption);
56
65
  const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
57
66
  recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
58
- raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}) });
67
+ raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
68
+ ...(o.due ? { dueAt: dueAt(o.due) } : {}) });
59
69
  emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
60
70
  }));
61
71
  root.command('list').description('결정 목록 (기본 열린 것)')
@@ -0,0 +1,237 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { join, resolve } from 'node:path';
4
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
5
+ import type { Command } from 'commander';
6
+
7
+ /** Attribution is evidence about the executor, not about the person who requested the work. */
8
+ export type DependenceAttribution = 'claude-code' | 'elanous' | 'other' | 'unknown';
9
+ export type DependenceAxis = 'positions' | 'executions' | 'changes' | 'decisions';
10
+ export interface DependenceObservation {
11
+ at?: string;
12
+ runtime?: string;
13
+ agent?: string;
14
+ track?: string;
15
+ /** A direct attribution supplied by a source record; never inferred from the author. */
16
+ actor?: string;
17
+ }
18
+ export interface DependenceSource {
19
+ observations: readonly DependenceObservation[];
20
+ /** A missing source is different from an observed empty source. */
21
+ unmeasurable?: string;
22
+ }
23
+ export interface DependenceAxisResult {
24
+ status: 'measured' | 'unmeasurable';
25
+ total: number;
26
+ covered: number;
27
+ claudeCode: number;
28
+ elanous: number;
29
+ other: number;
30
+ unknown: number;
31
+ /** Records whose timestamp cannot place them inside or outside the requested window. Not in total. */
32
+ undated: number;
33
+ reason?: string;
34
+ }
35
+ export interface DependenceReport {
36
+ since: string;
37
+ until: string;
38
+ axes: Record<DependenceAxis, DependenceAxisResult>;
39
+ }
40
+ export interface DependenceGaugeDeps {
41
+ now?: () => Date;
42
+ readPositions?: () => DependenceSource;
43
+ readExecutions?: () => DependenceSource;
44
+ readChanges?: (window: { since: string; until: string }) => DependenceSource;
45
+ readDecisions?: () => DependenceSource;
46
+ out?: Pick<Console, 'log'>;
47
+ }
48
+
49
+ const axes = ['positions', 'executions', 'changes', 'decisions'] as const;
50
+ const identity = (value: unknown): DependenceAttribution => {
51
+ if (typeof value !== 'string' || !value.trim()) return 'unknown';
52
+ const normalized = value.trim().toLowerCase();
53
+ if (normalized === 'claude-code' || normalized === 'claude code') return 'claude-code';
54
+ if (normalized === 'elanous') return 'elanous';
55
+ if (normalized === 'unknown') return 'unknown';
56
+ return 'other';
57
+ };
58
+
59
+ /** Explicit per-event runtime wins over an agent label, which wins over the seat default. */
60
+ export function attributeDependence(row: DependenceObservation, positions: ReadonlyMap<string, DependenceAttribution>): DependenceAttribution {
61
+ if (row.actor?.trim()) return identity(row.actor);
62
+ if (row.runtime?.trim()) return identity(row.runtime);
63
+ if (row.agent?.trim()) return identity(row.agent);
64
+ return row.track ? (positions.get(row.track) ?? 'unknown') : 'unknown';
65
+ }
66
+
67
+ function instant(raw: string, label: string): Date {
68
+ // Date.parse silently normalizes impossible days (e.g. February 30), including timestamps.
69
+ const calendarDate = /^(\d{4}-\d{2}-\d{2})(?:T|$)/.exec(raw)?.[1];
70
+ if (calendarDate) {
71
+ const day = new Date(`${calendarDate}T00:00:00.000Z`);
72
+ if (!Number.isFinite(day.getTime()) || day.toISOString().slice(0, 10) !== calendarDate) {
73
+ throw new Error(`invalid ${label}: ${raw}`);
74
+ }
75
+ }
76
+ const date = new Date(calendarDate === raw ? `${raw}T00:00:00.000Z` : raw);
77
+ if (!Number.isFinite(date.getTime())) throw new Error(`invalid ${label}: ${raw}`);
78
+ return date;
79
+ }
80
+
81
+ function observationTime(raw: string | undefined): number | undefined {
82
+ if (!raw) return undefined;
83
+ const match = /^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.exec(raw);
84
+ if (!match) return undefined;
85
+ const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
86
+ if (day.toISOString().slice(0, 10) !== raw.slice(0, 10)) return undefined;
87
+ const time = Date.parse(raw);
88
+ return Number.isFinite(time) ? time : undefined;
89
+ }
90
+
91
+ export function dependenceWindow(since: string | undefined, until: string | undefined, now: Date): { since: string; until: string } {
92
+ const end = until ? instant(until, '--until') : now;
93
+ if (!Number.isFinite(end.getTime())) throw new Error('invalid current date');
94
+ // Date-only --until includes that entire UTC day; relative lookback is anchored to its end.
95
+ const inclusiveEnd = until && /^\d{4}-\d{2}-\d{2}$/.test(until)
96
+ ? new Date(end.getTime() + 86_400_000 - 1) : end;
97
+ const match = /^(\d+)d$/.exec(since ?? '7d');
98
+ const start = match ? new Date(inclusiveEnd.getTime() - Number(match[1]) * 86_400_000)
99
+ : instant(since ?? '7d', '--since');
100
+ if (!Number.isFinite(start.getTime()) || start > inclusiveEnd) throw new Error('invalid --since: start must be at or before --until');
101
+ return { since: start.toISOString(), until: inclusiveEnd.toISOString() };
102
+ }
103
+
104
+ export function readSeatRegistry(): DependenceSource {
105
+ try {
106
+ const raw = JSON.parse(readFileSync(resolve(import.meta.dir, '../../scripts/coord-tracks.json'), 'utf8')) as { tracks?: unknown };
107
+ if (!Array.isArray(raw.tracks)) return { observations: [], unmeasurable: 'seat registry has no tracks' };
108
+ return { observations: raw.tracks.filter((track): track is { id: string; title: string; runtime?: string } =>
109
+ typeof track === 'object' && track !== null && typeof track.id === 'string' && typeof track.title === 'string')
110
+ .map(track => ({ track: track.id, runtime: track.runtime })) };
111
+ } catch (error) {
112
+ return { observations: [], unmeasurable: `seat registry unreadable: ${error instanceof Error ? error.message : String(error)}` };
113
+ }
114
+ }
115
+
116
+ function unknownSource(description: string): DependenceSource {
117
+ return { observations: [], unmeasurable: description };
118
+ }
119
+
120
+ function stringField(value: unknown): string | undefined {
121
+ return typeof value === 'string' && value.trim() ? value : undefined;
122
+ }
123
+
124
+ function object(value: unknown): Record<string, unknown> {
125
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
126
+ }
127
+
128
+ export function readRunExecutions(directory = join(elanousStateRoot(), 'run-ledger')): DependenceSource {
129
+ try {
130
+ if (!existsSync(directory)) return unknownSource('run-ledger directory absent');
131
+ const observations: DependenceObservation[] = [];
132
+ for (const name of readdirSync(directory).filter(name => name.endsWith('.jsonl')).sort()) {
133
+ const lines = readFileSync(join(directory, name), 'utf8').split('\n').filter(Boolean);
134
+ let launch: DependenceObservation | undefined;
135
+ for (const line of lines) {
136
+ const entry = object(JSON.parse(line));
137
+ if (entry.event === 'run-origin') {
138
+ const data = object(entry.data);
139
+ launch = { at: stringField(entry.timestamp), runtime: stringField(data.runtime),
140
+ agent: stringField(data.agent), track: stringField(data.track) };
141
+ break;
142
+ }
143
+ if (!launch) launch = { at: stringField(entry.timestamp) };
144
+ }
145
+ if (launch) observations.push(launch);
146
+ }
147
+ return { observations };
148
+ } catch (error) {
149
+ return unknownSource(`run-ledger unreadable: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ }
152
+
153
+ /** 이 저장소의 실제 표지 — `Co-Authored-By:` 트레일러(작성자·커미터는 전부 같은 사람이라 쓰지 않는다).
154
+ * 하니스 착지(`elanous pod child`) > codex > Claude 순 — 한 병합에 여러 표지면 실제로 코드를 쓴 쪽이 앞선다. */
155
+ export function runtimeFromCoAuthors(body: string): string | undefined {
156
+ const names = [...body.matchAll(/^co-authored-by:[^\S\r\n]*([^<\r\n]*)/gim)].map(m => m[1]!.trim().toLowerCase());
157
+ if (names.some(n => n.includes('elanous'))) return 'elanous';
158
+ if (names.some(n => n.includes('codex'))) return 'codex';
159
+ if (names.some(n => n.includes('claude'))) return 'claude-code';
160
+ return undefined;
161
+ }
162
+
163
+ export function readChangeCommits(window: { since: string; until: string }, repo = resolve(import.meta.dir, '../..')): DependenceSource {
164
+ // Git object reads only: author and committer are deliberately NOT treated as the executor.
165
+ // 착지 = main 의 첫 부모 줄(스쿼시 병합 하나 = 착지 하나) — `--all` 은 가지의 중간 커밋까지 세어 부풀린다.
166
+ const result = spawnSync('git', ['log', 'origin/main', '--first-parent', '--format=%cI%x1f%B%x1e', `--since-as-filter=${window.since}`, `--until=${window.until}`], {
167
+ cwd: repo, encoding: 'utf8', timeout: 15_000, maxBuffer: 16 * 1024 * 1024,
168
+ });
169
+ if (result.status !== 0 || result.error) return unknownSource(`git history unreadable: ${result.error?.message ?? result.stderr ?? result.status}`);
170
+ if (result.stdout.length >= 16 * 1024 * 1024 - 1024) return unknownSource('git history exceeds gauge read limit');
171
+ const observations = result.stdout.split('\x1e').filter(record => record.includes('\x1f')).map(record => {
172
+ const [at, body] = record.trim().split('\x1f', 2);
173
+ const attribution = /(?:^|\n)(?:Executed-By|Agent-Runtime|Runtime):[^\S\r\n]*([^\r\n]*)/im.exec(body ?? '');
174
+ return { at, runtime: attribution?.[1]?.trim() ?? runtimeFromCoAuthors(body ?? '') };
175
+ });
176
+ return { observations };
177
+ }
178
+
179
+ /** No mutation of ledgers or state; absent attribution data is reported, never guessed from an author or requester. */
180
+ export function measureDependence(deps: DependenceGaugeDeps = {}, options: { since?: string; until?: string } = {}): DependenceReport {
181
+ const window = dependenceWindow(options.since, options.until, (deps.now ?? (() => new Date()))());
182
+ const sources: Record<DependenceAxis, DependenceSource> = {
183
+ positions: (deps.readPositions ?? readSeatRegistry)(),
184
+ executions: (deps.readExecutions ?? readRunExecutions)(),
185
+ changes: (deps.readChanges ?? readChangeCommits)(window),
186
+ decisions: (deps.readDecisions ?? (() => unknownSource('decision actor cannot be measured from the available data')))(),
187
+ };
188
+ const positions = new Map<string, DependenceAttribution>();
189
+ for (const row of sources.positions.observations) {
190
+ if (row.track) positions.set(row.track, identity(row.runtime));
191
+ }
192
+ const result = {} as Record<DependenceAxis, DependenceAxisResult>;
193
+ for (const axis of axes) {
194
+ const source = sources[axis];
195
+ const counts: DependenceAxisResult = { status: source.unmeasurable ? 'unmeasurable' : 'measured', total: 0, covered: 0, claudeCode: 0, elanous: 0, other: 0, unknown: 0, undated: 0,
196
+ ...(source.unmeasurable ? { reason: source.unmeasurable } : {}) };
197
+ for (const row of source.observations) {
198
+ if (axis !== 'positions') {
199
+ const at = observationTime(row.at);
200
+ if (at === undefined) { counts.undated++; continue; }
201
+ if (at < Date.parse(window.since) || at > Date.parse(window.until)) continue;
202
+ }
203
+ counts.total++;
204
+ const attribution = attributeDependence(row, positions);
205
+ if (attribution === 'unknown') counts.unknown++;
206
+ else {
207
+ counts.covered++;
208
+ if (attribution === 'claude-code') counts.claudeCode++;
209
+ else if (attribution === 'elanous') counts.elanous++;
210
+ else counts.other++;
211
+ }
212
+ }
213
+ result[axis] = counts;
214
+ }
215
+ return { ...window, axes: result };
216
+ }
217
+
218
+ export function formatDependenceReport(report: DependenceReport): string {
219
+ return [
220
+ `Dependence · ${report.since} — ${report.until}`,
221
+ ...axes.map(axis => {
222
+ const row = report.axes[axis];
223
+ return `${axis}: ${row.status}${row.reason ? ` (${row.reason})` : ''} · coverage ${row.covered}/${row.total} · claude-code ${row.claudeCode} · elanous ${row.elanous} · other ${row.other} · unknown ${row.unknown} · undated ${row.undated}`;
224
+ }),
225
+ ].join('\n');
226
+ }
227
+
228
+ /** Register below the existing `self` command rather than creating a second top-level command. */
229
+ export function registerDependenceCommand(self: Command, deps: DependenceGaugeDeps = {}): void {
230
+ self.command('dependence').description('Read-only attribution gauge (unknown is not zero)')
231
+ .option('--since <date>', 'UTC date or lookback period (default 7d)', '7d')
232
+ .option('--until <date>', 'UTC date or timestamp').option('--json', 'JSON output')
233
+ .action((options: { since?: string; until?: string; json?: boolean }) => {
234
+ const report = measureDependence(deps, options);
235
+ (deps.out ?? console).log(options.json ? JSON.stringify(report) : formatDependenceReport(report));
236
+ });
237
+ }