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,220 @@
1
+ import { createPublicKey, randomBytes, verify } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import type { ServerWebSocket } from 'bun';
4
+ import { MAX_FRAME_BYTES } from './channel.js';
5
+
6
+ const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
7
+ const MAX_CLIENTS_PER_SERVER = 8;
8
+ const BYTES_PER_SECOND = 1024 * 1024;
9
+ const BURST_BYTES = 2 * BYTES_PER_SECOND;
10
+ const PING_INTERVAL_MS = 30_000;
11
+ const DEAD_PEER_MS = 90_000;
12
+ const AUTH_TIMEOUT_MS = 10_000;
13
+
14
+ type SocketData = { role: 'host' | 'client'; serverId: string; clientId?: number; challenge?: string; authenticated: boolean; lastSeen: number; tokens: number; refillAt: number; authTimer?: ReturnType<typeof setTimeout> };
15
+ type Socket = ServerWebSocket<SocketData>;
16
+ type Room = { host?: Socket; clients: Map<number, Socket>; nextId: number; bytes: number; failures: Record<string, number> };
17
+
18
+ export interface RelayServerOptions {
19
+ port?: number;
20
+ hostname?: string;
21
+ /** Ed25519 signing public keys (base64url raw 32 bytes), pinned by X25519-derived serverId. */
22
+ trustedHostKeys?: ReadonlyMap<string, string>;
23
+ }
24
+
25
+ /** Load operator-provisioned pins, never keys claimed by connecting hosts. */
26
+ export function loadTrustedHostKeys(file: string): ReadonlyMap<string, string> {
27
+ const raw: unknown = JSON.parse(readFileSync(file, 'utf8'));
28
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('invalid relay trusted host keys');
29
+ const result = new Map<string, string>();
30
+ for (const [id, key] of Object.entries(raw)) {
31
+ if (!/^[a-f0-9]{64}$/.test(id) || typeof key !== 'string' || !decodeKey(key)) {
32
+ throw new Error('invalid relay trusted host key entry');
33
+ }
34
+ result.set(id, key);
35
+ }
36
+ return result;
37
+ }
38
+
39
+ export function challengeMessage(serverId: string, nonce: string): Uint8Array {
40
+ return Buffer.from(`elanous-relay-v1:${serverId}:${nonce}`, 'utf8');
41
+ }
42
+
43
+ function decodeKey(value: string): Buffer | undefined {
44
+ if (!/^[A-Za-z0-9_-]{43}$/.test(value)) return undefined;
45
+ const decoded = Buffer.from(value, 'base64url');
46
+ return decoded.length === 32 && decoded.toString('base64url') === value ? decoded : undefined;
47
+ }
48
+
49
+ function encodeClientId(id: number, payload: Uint8Array): Uint8Array {
50
+ const result = new Uint8Array(payload.length + 4);
51
+ new DataView(result.buffer).setUint32(0, id, false);
52
+ result.set(payload, 4);
53
+ return result;
54
+ }
55
+
56
+ /** The relay never parses or logs encrypted channel payloads. Host binary frames carry a
57
+ * four-byte big-endian clientId prefix; phone binary frames are raw ciphertext. */
58
+ export function startRelayServer(options: RelayServerOptions = {}) {
59
+ if (!options.trustedHostKeys?.size) throw new Error('relay trusted host keys required');
60
+ const trustedHostKeys = new Map(options.trustedHostKeys);
61
+ for (const [id, key] of trustedHostKeys) {
62
+ if (!/^[a-f0-9]{64}$/.test(id) || !decodeKey(key)) throw new Error('invalid relay trusted host key entry');
63
+ }
64
+ const rooms = new Map<string, Room>();
65
+ const sockets = new Set<Socket>();
66
+ const roomFor = (id: string): Room => {
67
+ let room = rooms.get(id);
68
+ if (!room) { room = { clients: new Map(), nextId: 1, bytes: 0, failures: {} }; rooms.set(id, room); }
69
+ return room;
70
+ };
71
+ const fail = (room: Room, reason: string) => { room.failures[reason] = (room.failures[reason] ?? 0) + 1; };
72
+ const sendControl = (ws: Socket, value: object) => { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(value)); };
73
+ const drop = (ws: Socket) => {
74
+ const data = ws.data;
75
+ if (data.authTimer) clearTimeout(data.authTimer);
76
+ sockets.delete(ws);
77
+ const room = rooms.get(data.serverId);
78
+ if (!room) return;
79
+ if (!data.authenticated) {
80
+ if (!room.host && room.clients.size === 0 &&
81
+ !Array.from(sockets).some(peer => peer.data.serverId === data.serverId)) rooms.delete(data.serverId);
82
+ return;
83
+ }
84
+ if (data.role === 'host' && room.host === ws) {
85
+ room.host = undefined;
86
+ const clients = [...room.clients.values()];
87
+ room.clients.clear();
88
+ for (const client of clients) client.close(1012, 'host disconnected');
89
+ } else if (data.role === 'client' && data.clientId !== undefined && room.clients.get(data.clientId) === ws) {
90
+ room.clients.delete(data.clientId);
91
+ if (room.host) sendControl(room.host, { type: 'client-disconnected', clientId: data.clientId });
92
+ }
93
+ if (!room.host && room.clients.size === 0 &&
94
+ !Array.from(sockets).some(peer => peer.data.serverId === data.serverId)) rooms.delete(data.serverId);
95
+ };
96
+ const capacity = (ws: Socket, length: number): boolean => {
97
+ const data = ws.data;
98
+ const now = Date.now();
99
+ data.tokens = Math.min(BURST_BYTES, data.tokens + (now - data.refillAt) * BYTES_PER_SECOND / 1000);
100
+ data.refillAt = now;
101
+ if (length > data.tokens) return false;
102
+ data.tokens -= length;
103
+ return true;
104
+ };
105
+ const server = Bun.serve<SocketData>({
106
+ hostname: options.hostname ?? '127.0.0.1', port: options.port ?? 0,
107
+ fetch(req, server) {
108
+ const url = new URL(req.url);
109
+ if (url.pathname !== '/host' && url.pathname !== '/client') return new Response('not found', { status: 404 });
110
+ const serverId = url.searchParams.get('serverId');
111
+ if (!serverId || !/^[a-f0-9]{64}$/.test(serverId)) return new Response('invalid serverId', { status: 400 });
112
+ if (!trustedHostKeys.has(serverId)) return new Response('unknown serverId', { status: 403 });
113
+ if (req.headers.get('upgrade')?.toLowerCase() !== 'websocket') return new Response('upgrade required', { status: 426 });
114
+ const role = url.pathname === '/host' ? 'host' : 'client';
115
+ const now = Date.now();
116
+ if (!server.upgrade(req, { data: { role, serverId, authenticated: false, lastSeen: now, tokens: BURST_BYTES, refillAt: now } })) {
117
+ return new Response('upgrade required', { status: 426 });
118
+ }
119
+ return undefined;
120
+ },
121
+ websocket: {
122
+ maxPayloadLength: MAX_FRAME_BYTES + 4,
123
+ open(ws) {
124
+ sockets.add(ws);
125
+ const data = ws.data;
126
+ const room = roomFor(data.serverId);
127
+ if (data.role === 'host') {
128
+ if (room.host) { fail(room, 'duplicate-host'); ws.close(1008, 'host already connected'); return; }
129
+ data.challenge = randomBytes(32).toString('base64url');
130
+ sendControl(ws, { type: 'challenge', nonce: data.challenge });
131
+ } else {
132
+ if (!room.host || room.clients.size >= MAX_CLIENTS_PER_SERVER) {
133
+ fail(room, !room.host ? 'host-offline' : 'client-limit');
134
+ ws.close(1008, !room.host ? 'host offline' : 'client limit reached');
135
+ return;
136
+ }
137
+ const id = room.nextId++;
138
+ data.clientId = id;
139
+ data.authenticated = true;
140
+ room.clients.set(id, ws);
141
+ sendControl(ws, { type: 'connected', clientId: id });
142
+ sendControl(room.host, { type: 'client-connected', clientId: id });
143
+ }
144
+ if (!data.authenticated) data.authTimer = setTimeout(() => ws.close(1008, 'authentication timeout'), AUTH_TIMEOUT_MS);
145
+ },
146
+ message(ws, message) {
147
+ const data = ws.data;
148
+ const room = rooms.get(data.serverId);
149
+ if (!room) { ws.close(1008, 'room missing'); return; }
150
+ data.lastSeen = Date.now();
151
+ if (!data.authenticated) {
152
+ if (data.role !== 'host' || typeof message !== 'string') { fail(room, 'invalid-auth'); ws.close(1008, 'authentication required'); return; }
153
+ let auth: unknown;
154
+ try { auth = JSON.parse(message); } catch { /* reject below */ }
155
+ const value = auth as { type?: unknown; publicKey?: unknown; signature?: unknown } | undefined;
156
+ const key = typeof value?.publicKey === 'string' ? decodeKey(value.publicKey) : undefined;
157
+ const signature = typeof value?.signature === 'string' && /^[A-Za-z0-9_-]{86}$/.test(value.signature)
158
+ ? Buffer.from(value.signature, 'base64url') : undefined;
159
+ const expected = trustedHostKeys.get(data.serverId);
160
+ let valid = false;
161
+ if (value?.type === 'auth' && key && signature?.length === 64 && data.challenge &&
162
+ value.publicKey === expected) {
163
+ try {
164
+ valid = verify(null, challengeMessage(data.serverId, data.challenge),
165
+ createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, key]), format: 'der', type: 'spki' }), signature);
166
+ } catch { /* invalid public key */ }
167
+ }
168
+ if (!valid || (room.host && room.host !== ws)) { fail(room, 'invalid-auth'); ws.close(1008, 'invalid host signature'); return; }
169
+ data.authenticated = true;
170
+ data.challenge = undefined;
171
+ if (data.authTimer) clearTimeout(data.authTimer);
172
+ room.host = ws;
173
+ sendControl(ws, { type: 'authenticated' });
174
+ return;
175
+ }
176
+ if (typeof message === 'string') { fail(room, 'plaintext-data'); ws.close(1003, 'binary ciphertext required'); return; }
177
+ const bytes = new Uint8Array(message);
178
+ if (bytes.length > MAX_FRAME_BYTES + (data.role === 'host' ? 4 : 0) || bytes.length < (data.role === 'host' ? 5 : 1)) {
179
+ fail(room, 'invalid-frame'); ws.close(1009, 'invalid frame size'); return;
180
+ }
181
+ if (!capacity(ws, bytes.length)) { fail(room, 'rate-limit'); ws.close(1008, 'rate limit exceeded'); return; }
182
+ if (data.role === 'client') {
183
+ if (!room.host || data.clientId === undefined) { ws.close(1012, 'host offline'); return; }
184
+ room.bytes += bytes.length;
185
+ room.host.send(encodeClientId(data.clientId, bytes));
186
+ } else {
187
+ if (room.host !== ws) { ws.close(1008, 'host replaced'); return; }
188
+ const clientId = new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, false);
189
+ const client = room.clients.get(clientId);
190
+ if (!client) { fail(room, 'unknown-client'); return; }
191
+ room.bytes += bytes.length - 4;
192
+ client.send(bytes.subarray(4));
193
+ }
194
+ },
195
+ pong(ws) { ws.data.lastSeen = Date.now(); },
196
+ close(ws) { drop(ws); },
197
+ },
198
+ });
199
+ const heartbeat = setInterval(() => {
200
+ for (const ws of sockets) {
201
+ if (Date.now() - ws.data.lastSeen > DEAD_PEER_MS) ws.close(1001, 'heartbeat timeout');
202
+ else ws.ping();
203
+ }
204
+ }, PING_INTERVAL_MS);
205
+ heartbeat.unref();
206
+ return {
207
+ server,
208
+ get port() { return server.port; },
209
+ stats(serverId: string) {
210
+ const room = rooms.get(serverId);
211
+ return room ? { host: !!room.host, clients: room.clients.size, bytes: room.bytes, failures: { ...room.failures } } :
212
+ { host: false, clients: 0, bytes: 0, failures: {} as Record<string, number> };
213
+ },
214
+ stop() {
215
+ clearInterval(heartbeat);
216
+ for (const ws of sockets) ws.close(1001, 'relay stopping');
217
+ server.stop(true);
218
+ },
219
+ };
220
+ }
@@ -4,9 +4,12 @@ import { dirname, join } from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { debug } from '../debug/log.js';
6
6
  import { releaseLedgerRoot } from '../instance/resolve.js';
7
+ import { CliUserError } from '../cli/cli-user-error.js';
7
8
 
8
9
  export type ChecklistStatus = 'green' | 'yellow' | 'red' | 'done';
9
10
  export type ChecklistDisposition = 'move' | 'known-issue' | 'block';
11
+ /** `screen` = a five-surface feature cell — its evidence carries a `짝:` line (MANUAL-five-surface-parity §A). */
12
+ export type ChecklistKind = 'screen';
10
13
  export interface ChecklistItem {
11
14
  id: string;
12
15
  title: string;
@@ -14,6 +17,7 @@ export interface ChecklistItem {
14
17
  owner?: string;
15
18
  evidence?: string;
16
19
  disposition?: ChecklistDisposition;
20
+ kind?: ChecklistKind;
17
21
  updatedAt: string;
18
22
  updatedBy: string;
19
23
  }
@@ -54,7 +58,7 @@ function releasedVersion(): string {
54
58
  }
55
59
 
56
60
  function pathFor(v: string): string {
57
- if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new Error(`체크리스트 판이 아니다: ${v}`);
61
+ if (!/^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(v)) throw new CliUserError(`체크리스트 판이 아니다: ${v}`);
58
62
  return join(releaseLedgerRoot(), 'release', v, 'checklist.json');
59
63
  }
60
64
 
@@ -109,27 +113,29 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
109
113
 
110
114
  function refresh(data: Checklist): void { data.released = releasedVersion(); data.dev = devVersion(); }
111
115
 
112
- export function addItem(v: string, input: { id: string; title: string; owner?: string }): Checklist {
113
- if (!input.id.trim()) throw new Error('칸 id 가 비었다');
114
- if (!input.title.trim()) throw new Error('칸 제목이 비었다');
116
+ export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind }): Checklist {
117
+ if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
118
+ if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
119
+ if (input.kind !== undefined && input.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen');
115
120
  return mutate(v, (data) => {
116
- if (data.items.some((item) => item.id === input.id)) throw new Error(`이미 있는 칸: ${input.id}`);
121
+ if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
117
122
  const by = process.env.ELANOUS_TRACK || 'cli';
118
123
  const at = new Date().toISOString();
119
- const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), updatedAt: at, updatedBy: by };
124
+ const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), updatedAt: at, updatedBy: by };
120
125
  data.items.push(item);
121
126
  change(data, item.id, 'add', null, item, by, at);
122
127
  return true;
123
128
  });
124
129
  }
125
130
 
126
- export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition }, by: string): Checklist {
131
+ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind }, by: string): Checklist {
127
132
  return mutate(v, (data) => {
128
133
  const item = data.items.find((i) => i.id === id);
129
- if (!item) throw new Error(`없는 칸: ${id}`);
130
- if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new Error(`잘못된 상태: ${patch.status}`);
131
- if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new Error(`잘못된 처분: ${patch.disposition}`);
132
- const fields = (['evidence', 'owner', 'status', 'disposition'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
134
+ if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
135
+ if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new CliUserError(`잘못된 상태: ${patch.status}`);
136
+ if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new CliUserError(`잘못된 처분: ${patch.disposition}`);
137
+ if (patch.kind !== undefined && patch.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${patch.kind}`, 'screen');
138
+ const fields = (['evidence', 'owner', 'status', 'disposition', 'kind'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
133
139
  if (fields.length === 0) return false;
134
140
  const at = new Date().toISOString();
135
141
  for (const field of fields) {
@@ -147,7 +153,7 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
147
153
  export function removeItem(v: string, id: string, by: string): Checklist {
148
154
  return mutate(v, (data) => {
149
155
  const index = data.items.findIndex((i) => i.id === id);
150
- if (index < 0) throw new Error(`없는 칸: ${id}`);
156
+ if (index < 0) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
151
157
  const at = new Date().toISOString();
152
158
  const [item] = data.items.splice(index, 1);
153
159
  change(data, id, 'remove', item, null, by, at);
@@ -179,11 +185,29 @@ export interface ChecklistGate {
179
185
  blocked: string[];
180
186
  moved: string[];
181
187
  knownIssues: Array<{ id: string; title: string; evidence: string }>;
188
+ /** Screen cells whose `짝:` line is missing or has an untracked ⏳ — a warning; it never changes `ok`. */
189
+ parity?: Array<{ id: string; why: string }>;
190
+ }
191
+
192
+ const PARITY_LINE = /짝:\s*PWA\s*(.+?)\s*·\s*데스크톱\s*(.+?)\s*·\s*폴드\s*(.+?)\s*·\s*아이폰\s*(.+?)\s*·\s*아이패드\s*(.+?)\s*$/m;
193
+ const PARITY_SURFACES = ['PWA', '데스크톱', '폴드', '아이폰', '아이패드'] as const;
194
+
195
+ /** Why a screen cell's evidence fails the five-surface rule, or null when it passes. */
196
+ export function parityGap(evidence: string | undefined): string | null {
197
+ if (!evidence || !/짝:/.test(evidence)) return '근거에 짝: 줄이 없다';
198
+ const match = PARITY_LINE.exec(evidence);
199
+ if (!match) return '짝: 줄에 다섯 열(PWA · 데스크톱 · 폴드 · 아이폰 · 아이패드)이 다 없다';
200
+ const untracked = PARITY_SURFACES.filter((_, i) => match[i + 1]!.includes('⏳') && !/\(칸 /.test(match[i + 1]!));
201
+ return untracked.length ? `⏳ 에 (칸 …) 번호가 없다: ${untracked.join(', ')}` : null;
182
202
  }
183
203
 
184
204
  export function checklistGate(v: string): ChecklistGate {
185
- const result: ChecklistGate = { ok: true, red: [], undecided: [], blocked: [], moved: [], knownIssues: [] };
205
+ const result: ChecklistGate = { ok: true, red: [], undecided: [], blocked: [], moved: [], knownIssues: [], parity: [] };
186
206
  for (const item of listChecklist(v).items) {
207
+ if (item.kind === 'screen') {
208
+ const why = parityGap(item.evidence);
209
+ if (why) result.parity!.push({ id: item.id, why });
210
+ }
187
211
  if (item.status === 'red') result.red.push(item.id);
188
212
  if (item.status !== 'yellow') continue;
189
213
  if (item.disposition === 'block') result.blocked.push(item.id);
@@ -341,6 +341,7 @@ export function buildSelfDevDecomposePrompt(feature: string, maxTasks = DEFAULT_
341
341
  '## Rules',
342
342
  `- Emit at most ${maxTasks} sub-features. Fewer is better — do NOT over-split, except when a sub-feature mixes ${SELF_DEV_CONCERN_SPLIT_THRESHOLD} or more concern classes — ${SELF_DEV_CONCERN_CLASSES.join(' / ')} — split it into sibling sub-features.`,
343
343
  '- SEPARATE DEFINE FROM WIRE: adding a new function/type/export is a SEPARATE task from wiring it into an existing runtime call site. A task that both defines a new symbol AND integrates it across modules tends to leave dead-code (defined but never called) that fails integration gates. Pattern: (a) add the function + its unit test; then (b) a dependent task that wires it into the specific existing call site — name the exact `file.ts:function` to modify.',
344
+ '- Exception to SEPARATE DEFINE FROM WIRE: Keep a definition and its registration/wiring in the same sub-feature — a handler with its route, a command with its CLI registration, a node with its graph/recipe entry.',
344
345
  '- Each sub-feature must be a self-contained, mergeable unit of work.',
345
346
  '- `dependsOn`: list the `id`s of sub-features that MUST land first (e.g. a shared',
346
347
  ' type/interface before its consumers). Independent sub-features share NO dependency',
@@ -1871,7 +1871,10 @@ async function runDevPipelineDispatch(
1871
1871
  observeDevSelection('completion', plan.completion, plan.completionSource, plan.completionSource === 'request' ? spec.completion : undefined, entryRoute, spec.runId, plan.entrance, plan.entranceUnstamped);
1872
1872
  observeDevSelection('autoReview', plan.autoReview, plan.autoReviewSource, plan.autoReviewSource === 'request' ? spec.autoReview : undefined, entryRoute, spec.runId, plan.entrance, plan.entranceUnstamped);
1873
1873
  assertDispatchHonorsOptions(plan); // 미이행 옵션 거부(수락 후 무시 금지)
1874
- emitHumanProgress(completionAnnouncement(plan.completion), spec.humanReadableOutput !== false, deps.progress);
1874
+ // CHAT1 — a plain chat turn (`interactive`) is not a dev run: its completion/base lines are noise to a person.
1875
+ // The selection is still observed above; only the human-facing lines are dev-run-only.
1876
+ const devRunLines = plan.dispatch !== 'interactive';
1877
+ if (devRunLines) emitHumanProgress(completionAnnouncement(plan.completion), spec.humanReadableOutput !== false, deps.progress);
1875
1878
  if (spec.notice) emitHumanProgress(spec.notice, spec.humanReadableOutput !== false, deps.progress);
1876
1879
 
1877
1880
  if (plan.dispatch === 'parallel') {
@@ -1899,8 +1902,8 @@ async function runDevPipelineDispatch(
1899
1902
  rule: plan.baseSelection?.rule ?? 'unspecified',
1900
1903
  evidence: plan.baseSelection?.evidence ?? 'not applicable',
1901
1904
  });
1902
- emitHumanProgress(formatDevBaseSelectionAnnouncement(plan), spec.humanReadableOutput !== false, deps.progress);
1903
- const nonDefaultBaseWarning = formatDevNonDefaultBaseWarning(plan);
1905
+ if (devRunLines) emitHumanProgress(formatDevBaseSelectionAnnouncement(plan), spec.humanReadableOutput !== false, deps.progress);
1906
+ const nonDefaultBaseWarning = devRunLines ? formatDevNonDefaultBaseWarning(plan) : undefined;
1904
1907
  if (nonDefaultBaseWarning) {
1905
1908
  emitHumanProgress(nonDefaultBaseWarning, spec.humanReadableOutput !== false, deps.progress);
1906
1909
  }
@@ -987,6 +987,23 @@ async function deleteLabelledJobs(runId: string, selectedTargets: readonly { con
987
987
  return counts.reduce((sum, count) => sum + count, 0);
988
988
  }
989
989
 
990
+ /** `self-dev-runs/<runId>/pid.json` — what `harness stop <runId>` reads (start time guards PID reuse).
991
+ * HS1: written as soon as the run ID exists, not only when the jobs start — pod planning and image sync can
992
+ * take minutes before that, and a stop in that window found «no process» (10-01 · three runs). */
993
+ export function writeRunPidRecord(runId: string, runsDir: string = selfDevRunsDir()): string {
994
+ const pidPath = join(runsDir, runId, 'pid.json');
995
+ mkdirSync(join(runsDir, runId), { recursive: true });
996
+ const temporary = `${pidPath}.${process.pid}.${randomUUID()}.tmp`;
997
+ const fd = openSync(temporary, 'wx', 0o600);
998
+ try {
999
+ try {
1000
+ writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() - Math.floor(process.uptime() * 1000), argv0: process.argv0 }));
1001
+ } finally { closeSync(fd); }
1002
+ renameSync(temporary, pidPath);
1003
+ } catch (error) { unlinkSync(temporary); throw error; }
1004
+ return pidPath;
1005
+ }
1006
+
990
1007
  /**
991
1008
  * Run N self-dev jobs in parallel under the TOX dispatcher and resolve
992
1009
  * once every job reaches a terminal status.
@@ -1419,17 +1436,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1419
1436
  signals.on('SIGTERM', onTerm);
1420
1437
  signals.on('SIGINT', onInt);
1421
1438
  try {
1422
- if (pidPath) {
1423
- mkdirSync(join(opts.runsDir ?? selfDevRunsDir(), runId!), { recursive: true });
1424
- const temporary = `${pidPath}.${process.pid}.${randomUUID()}.tmp`;
1425
- const fd = openSync(temporary, 'wx', 0o600);
1426
- try {
1427
- try {
1428
- writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() - Math.floor(process.uptime() * 1000), argv0: process.argv0 }));
1429
- } finally { closeSync(fd); }
1430
- renameSync(temporary, pidPath);
1431
- } catch (error) { unlinkSync(temporary); throw error; }
1432
- }
1439
+ if (pidPath) writeRunPidRecord(runId!, opts.runsDir ?? selfDevRunsDir());
1433
1440
  } catch (error) { cleanup(); reject(error); return; }
1434
1441
 
1435
1442
  // Current full result set: resumed-done goals + each running task's
@@ -0,0 +1,44 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { debug } from '../debug/log.js';
5
+ import { releaseLedgerRoot } from '../instance/resolve.js';
6
+ import type { SelfDevRunState } from './run-store.js';
7
+
8
+ export function mirrorDir(): string {
9
+ return join(releaseLedgerRoot(), 'self-dev-runs');
10
+ }
11
+
12
+ function mirrorPath(runId: string, dir: string): string {
13
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) throw new Error(`invalid self-dev run ID: ${runId}`);
14
+ return join(dir, `${runId}.json`);
15
+ }
16
+
17
+ /** Best-effort atomic copy in the machine ledger; never disrupts the primary checkpoint. */
18
+ export function mirrorRunRecord(state: SelfDevRunState, primaryDir: string): void {
19
+ let temp: string | undefined;
20
+ try {
21
+ const dir = mirrorDir();
22
+ if (resolve(primaryDir) === resolve(dir)) return;
23
+ const path = mirrorPath(state.runId, dir);
24
+ mkdirSync(dir, { recursive: true });
25
+ temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
26
+ writeFileSync(temp, JSON.stringify(state, null, 2), { mode: 0o600 });
27
+ renameSync(temp, path);
28
+ } catch (error) {
29
+ if (temp) { try { unlinkSync(temp); } catch { /* no temporary file to remove */ } }
30
+ try { debug.log('self-dev.run-store', 'mirror-failed', { runId: state.runId, reason: String(error) }); } catch { /* keep the primary save fail-soft */ }
31
+ }
32
+ }
33
+
34
+ export function loadMirroredRunRecord(runId: string): SelfDevRunState | null {
35
+ try {
36
+ const record = JSON.parse(readFileSync(mirrorPath(runId, mirrorDir()), 'utf8')) as SelfDevRunState | null;
37
+ return record && record.runId === runId
38
+ && typeof record.createdAt === 'number' && Number.isFinite(record.createdAt)
39
+ && typeof record.updatedAt === 'number' && Number.isFinite(record.updatedAt)
40
+ && Array.isArray(record.results) ? record : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
@@ -27,6 +27,7 @@ import type { RunIdSource } from '../harness/harness-space.js';
27
27
  import { withFileLockSync } from '../storage/file-lock.js';
28
28
  import type { SelfDevJobResult, SelfDevGoal } from './orchestrate.js';
29
29
  import type { SupervisorStopReason } from './run-supervisor.js';
30
+ import { loadMirroredRunRecord, mirrorRunRecord } from './run-record-mirror.js';
30
31
 
31
32
  export interface SelfDevRunParticipant {
32
33
  id: string;
@@ -151,18 +152,26 @@ export function saveSelfDevRun(state: SelfDevRunState, dir = selfDevRunsDir()):
151
152
  withFileLockSync(join(dir, `${state.runId}.lock`), () => {
152
153
  const current = loadSelfDevRunFromPath(runPath(state.runId, dir));
153
154
  const participants = mergeParticipants(current?.participants, state.participants);
154
- writeSelfDevRun({
155
+ const saved = {
155
156
  ...state,
156
157
  ...(participants === undefined ? {} : { participants }),
157
158
  ...(current?.parkedResolution && !state.parkedResolution ? { parkedResolution: current.parkedResolution } : {}),
158
- }, dir);
159
+ };
160
+ writeSelfDevRun(saved, dir);
161
+ mirrorRunRecord(saved, dir);
159
162
  });
160
163
  } catch { /* fail-soft — a lost checkpoint just means no resume, not a crash */ }
161
164
  }
162
165
 
163
166
  /** Load a run checkpoint by id, or null if absent/corrupt. */
164
167
  export function loadSelfDevRun(runId: string, dir = selfDevRunsDir()): SelfDevRunState | null {
165
- return loadSelfDevRunFromPath(runPath(runId, dir));
168
+ const primary = loadSelfDevRunFromPath(runPath(runId, dir));
169
+ if (primary) return primary;
170
+ const mirrored = loadMirroredRunRecord(runId);
171
+ if (mirrored) {
172
+ try { debug.log('self-dev.run-store', 'read-from-mirror', { runId }); } catch { /* preserve fail-soft reads */ }
173
+ }
174
+ return mirrored;
166
175
  }
167
176
 
168
177
  /** Add one participant without losing registrations made by another caller for this run. */
@@ -4,8 +4,9 @@ import { runIsolationHardcodeGate } from '../../scripts/ci-isolation-hardcode-ga
4
4
  import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
5
5
  import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
6
6
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
7
+ import { runPublicExportLeakGate } from '../../scripts/ci-public-export-leak-gate.js';
7
8
 
8
- type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate';
9
+ type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak';
9
10
  type GateOutput = { args: string[]; cwd: string; log: (line: string) => void; error: (line: string) => void };
10
11
  type GateRunner = (out: GateOutput) => number;
11
12
 
@@ -27,6 +28,8 @@ export function runHarnessPolicyGates(input: {
27
28
  ['mock-module-restore-gate', input.gates?.['mock-module-restore-gate'] ?? runMockModuleRestoreGate],
28
29
  ['model-hardcode-gate', input.gates?.['model-hardcode-gate'] ?? runModelHardcodeGate],
29
30
  ['daemon-port-gate', input.gates?.['daemon-port-gate'] ?? ((out) => runDaemonPortGate({ log: out.log, error: out.error, cwd: out.cwd, args: [] }))],
31
+ // LEAK1 — the check release prepare fails on, before the harness lands (0.2.7: a mark in a test title).
32
+ ['public-export-leak', input.gates?.['public-export-leak'] ?? runPublicExportLeakGate],
30
33
  ];
31
34
  const failures: Array<{ gate: GateName; lines: string[] }> = [];
32
35
  for (const [gate, run] of runners) {
@@ -2,12 +2,23 @@ import type { MissionOrigin } from '../autopilot/mission-origin.js';
2
2
 
3
3
  export const REPORT_ORIGIN_ENV = 'ELANOUS_REPORT_ORIGIN';
4
4
 
5
- const CHANNELS = new Set<MissionOrigin['channel']>(['telegram', 'pwa', 'voice', 'cli', 'api', 'tui']);
5
+ const CHANNELS = new Set<MissionOrigin['channel']>(['telegram', 'pwa', 'voice', 'cli', 'api', 'tui', 'discord']);
6
+ const SNOWFLAKE = /^\d{1,20}$/;
6
7
 
7
8
  function isReportOrigin(value: unknown): value is MissionOrigin {
8
9
  if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
9
10
  const origin = value as Record<string, unknown>;
10
11
  if (!CHANNELS.has(origin.channel as MissionOrigin['channel'])) return false;
12
+ // Discord carries its own reply target (channel ⊕ optional thread snowflakes) — same shape as MissionOrigin.
13
+ if (origin.channel === 'discord') {
14
+ for (const key of Object.keys(origin)) {
15
+ if (!['channel', 'channelId', 'discordThreadId', 'botId', 'hitlMessageId'].includes(key)) return false;
16
+ }
17
+ return typeof origin.channelId === 'string' && SNOWFLAKE.test(origin.channelId)
18
+ && (origin.discordThreadId === undefined || (typeof origin.discordThreadId === 'string' && SNOWFLAKE.test(origin.discordThreadId)))
19
+ && (origin.botId === undefined || (typeof origin.botId === 'string' && origin.botId.length > 0))
20
+ && (origin.hitlMessageId === undefined || Number.isSafeInteger(origin.hitlMessageId));
21
+ }
11
22
  if (origin.channel === 'telegram' && (!Number.isSafeInteger(origin.chatId) || origin.chatId === 0)) return false;
12
23
  for (const key of Object.keys(origin)) {
13
24
  if (!['channel', 'chatId', 'botId', 'threadId', 'hitlMessageId'].includes(key)) return false;
@@ -30,6 +30,7 @@ import {
30
30
  import { buildMemoryInjection, buildMemoryInjectionLLM } from '../memory.js';
31
31
  import { getUserConfig } from '../user-config.js';
32
32
  import { budgetModel } from '../llm/model-defaults.js';
33
+ import { explicitTurnTier } from '../model-tier/turn-tier.js';
33
34
  import { recordTurn } from '../status/metrics.js';
34
35
  import {
35
36
  buildTerminalCapableTurn,
@@ -375,7 +376,18 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
375
376
  return trimToBudget(wire, opts.maxTokens ?? DEFAULT_TOKEN_BUDGET);
376
377
  };
377
378
 
378
- const provider = opts.provider ?? getProviderForConfig(opts.userConfig, opts.llmOpts?.model);
379
+ const explicitTier = opts.llmOpts?.model ? undefined : explicitTurnTier(opts.userConfig, opts.sessionId);
380
+ const llmOpts = explicitTier ? {
381
+ ...(opts.llmOpts ?? {}), model: explicitTier.model,
382
+ ...(explicitTier.reasoningEffort && !opts.llmOpts?.reasoningEffort ? { reasoningEffort: explicitTier.reasoningEffort } : {}),
383
+ } : opts.llmOpts;
384
+ if (explicitTier) {
385
+ debug.log('llm.tier', 'turn-tier-applied', {
386
+ sessionId: opts.sessionId, tier: explicitTier.tier, source: explicitTier.source, model: explicitTier.model,
387
+ reasoningEffort: llmOpts?.reasoningEffort ?? null,
388
+ });
389
+ }
390
+ const provider = opts.provider ?? getProviderForConfig(opts.userConfig, llmOpts?.model);
379
391
  let kept: LLMMessage[] = [];
380
392
  let dropped = 0;
381
393
  let used = 0;
@@ -394,7 +406,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
394
406
  specs: opts.tools,
395
407
  dispatch: rawDispatchTool,
396
408
  systemPromptParts: effectiveSystemPrompt ? [effectiveSystemPrompt] : [],
397
- ...(opts.llmOpts ? { llmOpts: opts.llmOpts } : {}),
409
+ ...(llmOpts ? { llmOpts } : {}),
398
410
  };
399
411
  const terminal = hasPtyShellTool
400
412
  ? isTerminalCapableTurn(terminalBase)
@@ -409,7 +421,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
409
421
  if (terminal) {
410
422
  effectiveSystemPrompt = terminal.systemPromptParts.join('\n\n');
411
423
  }
412
- const turnLlmOpts = terminal ? terminal.llmOpts : opts.llmOpts;
424
+ const turnLlmOpts = terminal ? terminal.llmOpts : llmOpts;
413
425
  ({ kept, dropped, used } = assembleTurnMessages(effectiveSystemPrompt));
414
426
  // Durable tool telemetry (추적성) — pair each call's args (onToolCall) with its
415
427
  // result (onToolResult) and persist a role:'tool' message so later "어떤 도구
@@ -477,7 +489,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
477
489
  }
478
490
  } else {
479
491
  ({ kept, dropped, used } = assembleTurnMessages(effectiveSystemPrompt));
480
- const plainOpts = opts.signal ? { ...(opts.llmOpts ?? {}), signal: opts.signal } : (opts.llmOpts ?? {});
492
+ const plainOpts = opts.signal ? { ...(llmOpts ?? {}), signal: opts.signal } : (llmOpts ?? {});
481
493
  const stream = provider.streamChat
482
494
  ? provider.streamChat(kept, plainOpts)
483
495
  : wrapTextOnly(provider.chat(kept, plainOpts));
@@ -529,7 +541,7 @@ export async function runTurn(opts: RunTurnOpts): Promise<RunTurnResult> {
529
541
  // status bar (CTX bar · $ · 🚀) reflects the latest numbers. Uses
530
542
  // rough estimates — providers that expose real `usage` fields can
531
543
  // override via opts.llmOpts in a later phase.
532
- const resolvedModel = opts.llmOpts?.model ?? provider.defaultModel;
544
+ const resolvedModel = llmOpts?.model ?? provider.defaultModel;
533
545
  recordTurn({
534
546
  model: resolvedModel,
535
547
  estimatedPromptText: [...messages, userMsg].map(m =>