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
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * - absorb → 흡수 원장(`ingestIntakeItems`) — URL 이 있으면 URL 마다, 없으면 글 한 덩이(PWA `absorbItemsFromText` 와 같은 규칙).
6
6
  * - tasks → TOX(`dispatchTaskCreate`) — 외부 출처(텔레그램 등)는 `external` 로 실어 M1(backlog ⊕ 승인 대기)을 탄다.
7
- * - graph → 하니스 ask(`handleHarnessAskPost` 를 요청 하나로 부른다 — 흐름을 복제하지 않는다).
7
+ * - graph → 자리 문서 요청은 A5 실행 요청, 나머지는 하니스 ask(`handleHarnessAskPost`).
8
8
  * - ask-human → 실행하지 않는다. 고를 갈래를 돌려준다.
9
9
  *
10
10
  * 로그엔 원문이 아니라 길이만 남긴다(앞문 RFC · `user-private`).
@@ -16,6 +16,7 @@ import type { ExternalTaskSource } from '../task-orchestrator/types.js';
16
16
  import { TASK_DEFAULTS } from '../task-orchestrator/types.js';
17
17
  import { decideIntakeFrontRoute, type IntakeFrontRouteDecision } from './front-route-rules.js';
18
18
  import { ingestIntakeItems, intakeItemId, type IntakeSource, type RawIntakeItem } from './items.js';
19
+ import { isCooPlannerRequest, isSeatDocRequest, submitSeatDocRequest, type SeatDocDeps } from './seat-doc-route.js';
19
20
 
20
21
  export type IntakeWorkTrack = 'absorb' | 'tasks' | 'graph';
21
22
 
@@ -40,6 +41,8 @@ export interface SubmitIntakeWorkDeps {
40
41
  ingest?: typeof ingestIntakeItems;
41
42
  createTask?: (input: CreateTaskInput) => Promise<{ taskId?: string; deduplicated?: boolean; output: string }>;
42
43
  askHarness?: (text: string, origin?: MissionOrigin) => Promise<{ acceptanceId?: string; error?: string }>;
44
+ seatDoc?: typeof submitSeatDocRequest;
45
+ seatDocDeps?: SeatDocDeps;
43
46
  handleHarnessAskPost?: typeof import('../nexus/api/harness-api.js').handleHarnessAskPost;
44
47
  log?: (event: string, data: Record<string, unknown>) => void;
45
48
  }
@@ -130,6 +133,11 @@ export async function submitIntakeWork(
130
133
  return { ok: true, track: 'tasks', taskId: res.taskId, deduplicated: res.deduplicated ?? false };
131
134
  }
132
135
  const reportTo = input.origin.kind === 'external' ? input.origin.reportTo : undefined;
136
+ if (isSeatDocRequest(text) || isCooPlannerRequest(text)) {
137
+ const { id } = await (deps.seatDoc ?? submitSeatDocRequest)({ text, reportTo, deps: deps.seatDocDeps });
138
+ log('submitted', { ...base, acceptanceId: id });
139
+ return { ok: true, track: 'graph', acceptanceId: id };
140
+ }
133
141
  const res = await (deps.askHarness ? deps.askHarness(text, reportTo) : defaultAskHarness(text, reportTo, deps.handleHarnessAskPost));
134
142
  if (!res.acceptanceId) {
135
143
  log('failed', { ...base, reason: res.error ?? 'no-acceptance-id' });
@@ -0,0 +1,72 @@
1
+ import { debug } from '../debug/log.js';
2
+ import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
3
+ import {
4
+ submitIntakeWork,
5
+ type SubmitIntakeWorkDeps,
6
+ } from './submit-intake-work.js';
7
+ import type { TelegramWorkMessage } from './telegram-work.js';
8
+
9
+ export interface TelegramSeatWorkDeps extends SubmitIntakeWorkDeps {
10
+ submit?: typeof submitIntakeWork;
11
+ }
12
+
13
+ /** Addressed Telegram work goes through graph intake, not the chat turn. */
14
+ export async function handleTelegramSeatWork(
15
+ text: string,
16
+ msg: TelegramWorkMessage,
17
+ deps: TelegramSeatWorkDeps = {},
18
+ ): Promise<string | null> {
19
+ if (!text.startsWith('@')) return null;
20
+ const address = parseSeatAddress(text);
21
+ if (!address) return null;
22
+
23
+ const seats = address.seats.map((name) => ({ name, seat: resolveSeat(name) }));
24
+ const names = address.seats;
25
+ debug.log('seat-address.telegram', 'parsed', { seats: names });
26
+ const unknown = seats.filter(({ seat }) => !seat).map(({ name }) => `@${name}`);
27
+ if (unknown.length) {
28
+ debug.log('seat-address.telegram', 'rejected', { seats: names, reason: 'unknown-seat' });
29
+ return `어느 좌석을 말씀하시나요? ${unknown.join(', ')}은(는) 등록된 좌석이 아닙니다. 좌석을 확인해 다시 보내 주세요.`;
30
+ }
31
+ const body = address.body.trim();
32
+ if (!body) {
33
+ debug.log('seat-address.telegram', 'rejected', { seats: names, reason: 'empty-body' });
34
+ return '어떤 일을 맡길까요? 좌석 주소 뒤에 요청 내용을 적어 다시 보내 주세요.';
35
+ }
36
+
37
+ const reportTo = {
38
+ channel: 'telegram' as const,
39
+ chatId: msg.chatId,
40
+ ...(msg.botId ? { botId: msg.botId } : {}),
41
+ ...(msg.threadId ? { threadId: msg.threadId } : {}),
42
+ };
43
+ const replies: string[] = [];
44
+ for (const { seat } of seats) {
45
+ if (!seat) continue;
46
+ const label = seat.title ?? seat.id;
47
+ let result: Awaited<ReturnType<typeof submitIntakeWork>>;
48
+ try {
49
+ result = await (deps.submit ?? submitIntakeWork)({
50
+ text: `@${label} ${body}`,
51
+ track: 'graph',
52
+ origin: {
53
+ kind: 'external', ledgerSource: 'telegram-bot', provider: 'telegram',
54
+ ref: `telegram:${msg.chatId}:${msg.messageId}`, reportTo,
55
+ },
56
+ }, deps);
57
+ } catch (error) {
58
+ // 접수 문이 던져도 사람은 같은 대화에서 결과를 받는다(리뷰 must-fix: 예외 시 응답 누락).
59
+ debug.log('seat-address.telegram', 'rejected', { seats: [label], reason: 'submission-threw' });
60
+ replies.push(`@${label} 접수 실패 — ${String(error instanceof Error ? error.message : error).split('\n')[0]!.slice(0, 200)}`);
61
+ continue;
62
+ }
63
+ if (result.ok && result.track === 'graph') {
64
+ debug.log('seat-address.telegram', 'enqueued', { seats: [label] });
65
+ replies.push(`@${label} 접수번호: ${result.acceptanceId}`);
66
+ } else {
67
+ debug.log('seat-address.telegram', 'rejected', { seats: [label], reason: 'submission-failed' });
68
+ replies.push(`@${label} 접수 실패 — ${result.ok ? '접수번호가 없습니다' : result.reason.split('\n')[0]!.slice(0, 200)}`);
69
+ }
70
+ }
71
+ return replies.join('\n');
72
+ }
@@ -20,7 +20,30 @@ import { getUserConfig, type LLMProviderName, type ReasoningLevel } from '../use
20
20
  * openai-codex 눈금이고 openai 기본값을 덮어쓰지 않는다.
21
21
  * ⛔ Do not import this file from user-config — that is the cycle. */
22
22
  function activeProviderForDefaults(provider?: LLMProviderName): LLMProviderName {
23
- return provider ?? getUserConfig().llm.provider;
23
+ if (provider) return provider;
24
+ const configured = getUserConfig().llm.provider;
25
+ return configured === 'auto' ? decidedAutoProvider() : configured;
26
+ }
27
+
28
+ /** `auto` has no ladder of its own — the tier map answers it with Anthropic's model, so a grok-only
29
+ * (or codex-only) user asked for a Claude model at every tier call site and hit «set ANTHROPIC_API_KEY».
30
+ * Resolve `auto` the way the router does (decideProviderForConfig) and use that provider's ladder.
31
+ * Late require: llm.ts imports this module. */
32
+ let decideAuto: () => LLMProviderName | undefined = () => {
33
+ const { decideProviderForConfig } = require('../llm.js') as typeof import('../llm.js');
34
+ const decided = String(decideProviderForConfig(getUserConfig()).provider).replace(/^auto:/, '');
35
+ return decided && decided !== 'auto' ? decided as LLMProviderName : undefined;
36
+ };
37
+ function decidedAutoProvider(): LLMProviderName {
38
+ try { return decideAuto() ?? 'auto'; } catch { return 'auto'; }
39
+ }
40
+ /** Test seam — swap the auto decision. */
41
+ export function setAutoProviderDecisionForTest(fn: (() => LLMProviderName | undefined) | undefined): void {
42
+ decideAuto = fn ?? (() => {
43
+ const { decideProviderForConfig } = require('../llm.js') as typeof import('../llm.js');
44
+ const decided = String(decideProviderForConfig(getUserConfig()).provider).replace(/^auto:/, '');
45
+ return decided && decided !== 'auto' ? decided as LLMProviderName : undefined;
46
+ });
24
47
  }
25
48
 
26
49
  /** config/tier 해석이 «실패»했을 때만 타는 최후 폴백. 티어 사다리가 안 읽히는
package/src/llm.ts CHANGED
@@ -3942,7 +3942,8 @@ function providerDecisionCandidates(): Array<LLMProvider & { name: ProviderDecis
3942
3942
  return PROVIDER_DECISION_ORDER.map((name) => PROVIDERS[name]! as LLMProvider & { name: ProviderDecisionName });
3943
3943
  }
3944
3944
 
3945
- function codexOAuthAvailable(): boolean {
3945
+ /** Exported (OB1) so onboarding and `setup` judge «is there a usable ChatGPT/Codex login» exactly as the runtime does. */
3946
+ export function codexOAuthAvailable(): boolean {
3946
3947
  try {
3947
3948
  const account = resolveCodexAccount(process.env, { storedHome: (key) => loadTokens(key)?.codexHome });
3948
3949
  if (loadTokens(account.storeKey)) return true;
@@ -4478,9 +4479,9 @@ async function consumeProviderText(
4478
4479
  export async function streamLLM(
4479
4480
  messages: LLMMessage[],
4480
4481
  onChunk: (delta: string, full: string) => void,
4481
- opts: LLMOpts & { provider?: LLMProvider; onResolvedProvider?: (provider: string) => void } = {},
4482
+ opts: LLMOpts & { provider?: LLMProvider; initialProvider?: LLMProvider; onResolvedProvider?: (provider: string, model: string) => void } = {},
4482
4483
  ): Promise<string> {
4483
- let activeProvider = opts.provider ?? resolveDefaultProvider(opts.model);
4484
+ let activeProvider = opts.provider ?? opts.initialProvider ?? resolveDefaultProvider(opts.model);
4484
4485
  const attemptedProviders = new Set<string>([activeProvider.name]);
4485
4486
  let blockedProviders: ProviderFallbackAttempt[] = [];
4486
4487
  let modelOverride = opts.model;
@@ -4493,7 +4494,7 @@ export async function streamLLM(
4493
4494
  );
4494
4495
  activeProvider = finalized.provider;
4495
4496
  const activeModel = finalized.model;
4496
- opts.onResolvedProvider?.(activeProvider.name);
4497
+ opts.onResolvedProvider?.(activeProvider.name, activeModel);
4497
4498
  debug.log('llm.router', 'streamLLM', {
4498
4499
  provider: activeProvider.name,
4499
4500
  model: activeModel,
@@ -0,0 +1,127 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { isIP } from 'node:net';
3
+ import { resolve } from 'node:path';
4
+ import { parseDocument } from 'yaml';
5
+
6
+ export interface MachineRow {
7
+ id: string;
8
+ name: string;
9
+ specs: string;
10
+ locationStatus: string;
11
+ role: string;
12
+ duties: string;
13
+ }
14
+
15
+ export interface DeviceRow {
16
+ id: string;
17
+ name: string;
18
+ status: string;
19
+ purpose: string;
20
+ }
21
+
22
+ export interface MachineLedger {
23
+ title: string;
24
+ policy: string[];
25
+ machines: MachineRow[];
26
+ devices: DeviceRow[];
27
+ rules: string[];
28
+ changes: string[];
29
+ }
30
+
31
+ export const machineLedgerPath = (root = process.cwd()): string => resolve(root, 'docs/ops/machines.yaml');
32
+ export const machineMarkdownPath = (root = process.cwd()): string => resolve(root, 'docs/ops/LEDGER-machines-and-devices.md');
33
+
34
+ // The ledger rule: no secrets, IPs or accounts — token prefixes, key=value assignments, e-mail addresses.
35
+ const forbidden = /(?:sk-(?:proj-|live-|ant-)?|gh[pousr]_|github_pat_|glpat-|xox[abposr]-|tskey-|AKIA|AIza)[A-Za-z0-9_-]{8,}|\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:token|secret|password|passwd|api[_-]?key|key)\s*[=:]\s*\S|-----BEGIN [A-Z ]*PRIVATE KEY|[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/i;
36
+ // A value lands in a Markdown table cell: a pipe or line break would split the row.
37
+ const tableBreaking = /[|\r\n]/;
38
+ const containsIpv6 = (value: string): boolean => (value.match(/(?:[a-f\d]*:){2,}[a-f\d:.]*/gi) ?? []).some((candidate) => isIP(candidate.replace(/\.+$/, '')) === 6);
39
+ const idPattern = /^[a-z][a-z0-9-]*$/;
40
+ const keys = (value: Record<string, unknown>, allowed: string[], label: string): void => {
41
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`unknown ${label} field: ${key}`);
42
+ };
43
+ const record = (value: unknown, label: string): Record<string, unknown> => {
44
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`invalid ${label}`);
45
+ return value as Record<string, unknown>;
46
+ };
47
+ const text = (value: unknown, label: string): string => {
48
+ if (typeof value !== 'string' || !value.trim() || forbidden.test(value) || containsIpv6(value)) throw new Error(`invalid or secret-like ${label}`);
49
+ if (tableBreaking.test(value)) throw new Error(`${label} must not contain a pipe or line break`);
50
+ return value;
51
+ };
52
+ const list = (value: unknown, label: string): string[] => {
53
+ if (!Array.isArray(value)) throw new Error(`invalid ${label}`);
54
+ return value.map((entry, index) => text(entry, `${label}[${index}]`));
55
+ };
56
+
57
+ export function validateMachineLedger(value: unknown): MachineLedger {
58
+ const data = record(value, 'ledger');
59
+ keys(data, ['title', 'policy', 'machines', 'devices', 'rules', 'changes'], 'ledger');
60
+ if (!Array.isArray(data.machines) || !Array.isArray(data.devices)) throw new Error('invalid machines or devices');
61
+ const ids = new Set<string>();
62
+ const rows = <T extends { id: string }>(entries: unknown[], fields: string[], label: string): T[] => entries.map((entry, index) => {
63
+ const row = record(entry, `${label}[${index}]`);
64
+ keys(row, fields, label);
65
+ const result = Object.fromEntries(fields.map((field) => [field, text(row[field], `${label}.${field}`)])) as T;
66
+ if (!idPattern.test(result.id) || ids.has(result.id)) throw new Error(`empty, duplicate or invalid id: ${result.id}`);
67
+ ids.add(result.id);
68
+ return result;
69
+ });
70
+ return {
71
+ title: text(data.title, 'title'),
72
+ policy: list(data.policy, 'policy'),
73
+ machines: rows<MachineRow>(data.machines, ['id', 'name', 'specs', 'locationStatus', 'role', 'duties'], 'machines'),
74
+ devices: rows<DeviceRow>(data.devices, ['id', 'name', 'status', 'purpose'], 'devices'),
75
+ rules: list(data.rules, 'rules'),
76
+ changes: list(data.changes, 'changes'),
77
+ };
78
+ }
79
+
80
+ export function loadMachineLedger(root = process.cwd()): MachineLedger {
81
+ const document = parseDocument(readFileSync(machineLedgerPath(root), 'utf8'), { uniqueKeys: true });
82
+ if (document.errors.length) throw new Error(document.errors.map((error) => error.message).join('; '));
83
+ return validateMachineLedger(document.toJS());
84
+ }
85
+
86
+ export function renderLedgerMarkdown(ledger: MachineLedger): string {
87
+ const data = validateMachineLedger(ledger);
88
+ const names = (name: string) => name === '(미정)' ? name : name.split(' · ').map((part) => `\`${part}\``).join(' · ');
89
+ const portablePolicy = data.policy.find((line) => line.startsWith('창은 '));
90
+ const portableHeading = portablePolicy ? `## 2. 창 (${portablePolicy.slice('창은 '.length).replace(/\.$/, '')})` : '## 2. 창';
91
+ return [
92
+ `# ${data.title}`, '',
93
+ '> 생성 결과 — 보유·역할과 변경 이력의 단일 원본은 [`machines.yaml`](machines.yaml). 이 문서의 행·규칙·변경 이력은 직접 수정하지 않는다. YAML 수정 후 `bun bin/elanous.mjs --test machine render` 로 재생성하고 `bun bin/elanous.mjs --test machine render --check` 로 확인한다.',
94
+ ...data.policy.filter((line) => !line.startsWith('창은 ')).map((line) => `> ${line.replace('`docs/ops/machines.yaml` 에', 'YAML 에')}`),
95
+ '', '## 1. 계산 자원 (일하는 기계)',
96
+ '| 이름(tailnet) | 기종 · 사양 | 위치 · 상태 | 자리 | 맡은 일 |',
97
+ '|---|---|---|---|---|',
98
+ ...data.machines.map((row) => `| ${names(row.name)} | ${row.specs} | ${row.locationStatus} | ${row.role} | ${row.duties} |`),
99
+ '', portableHeading,
100
+ '| 기기 | 상태 | 용도 |', '|---|---|---|',
101
+ ...data.devices.map((row) => `| ${row.name} | ${row.status} | ${row.purpose} |`),
102
+ '', '## 3. 규칙', ...data.rules.map((rule) => `- ${rule}`),
103
+ '', '## 변경', ...data.changes.map((change) => `- ${change}`), '',
104
+ ].join('\n');
105
+ }
106
+
107
+ export function setMachineField(root: string, id: string, field: string, value: string): MachineLedger {
108
+ const ledger = loadMachineLedger(root);
109
+ const row = [...ledger.machines, ...ledger.devices].find((item) => item.id === id);
110
+ if (!row) throw new Error(`unknown machine id: ${id}`);
111
+ if (field === 'id' || !(field in row)) throw new Error(`unknown or immutable field: ${field}`);
112
+ text(value, field);
113
+ const updated = { ...row, [field]: value };
114
+ if ('specs' in row) ledger.machines[ledger.machines.findIndex((item) => item.id === id)] = updated as MachineRow;
115
+ else ledger.devices[ledger.devices.findIndex((item) => item.id === id)] = updated as DeviceRow;
116
+ validateMachineLedger(ledger);
117
+ const yamlPath = machineLedgerPath(root);
118
+ const original = readFileSync(yamlPath, 'utf8');
119
+ const document = parseDocument(original, { uniqueKeys: true });
120
+ const section = 'specs' in row ? 'machines' : 'devices';
121
+ const index = section === 'machines' ? ledger.machines.findIndex((item) => item.id === id) : ledger.devices.findIndex((item) => item.id === id);
122
+ document.setIn([section, index, field], value);
123
+ validateMachineLedger(document.toJS());
124
+ writeFileSync(yamlPath, document.toString(), 'utf8');
125
+ writeFileSync(machineMarkdownPath(root), renderLedgerMarkdown(ledger), 'utf8');
126
+ return ledger;
127
+ }
@@ -0,0 +1,28 @@
1
+ export const INTERNAL_REF_MARKERS = [
2
+ { marker: 'internal-doc-path', pattern: /docs\/(?:marketing|plans|goals|manual|roles|archive|measurements|brand|backlog|harness|system)(?=\/|[\s)\]}>.,;:!?'"`]|$)/g },
3
+ { marker: 'private-pr-number', pattern: /(?:PR #\d{4,}|#\d{5,})/g },
4
+ { marker: 'cut-hash', pattern: /(?:컷|cut) [0-9a-f]{7,40}(?![0-9a-f])/g },
5
+ ] as const;
6
+
7
+ export type InternalRefHit = { file: string; line: number; marker: typeof INTERNAL_REF_MARKERS[number]['marker'] };
8
+
9
+ export function scanPackInternalRefs(files: { path: string; text: string }[]): InternalRefHit[] {
10
+ const hits: InternalRefHit[] = [];
11
+ for (const file of files) {
12
+ for (const [index, line] of file.text.split(/\r?\n/).entries()) {
13
+ for (const { marker, pattern } of INTERNAL_REF_MARKERS) {
14
+ for (const match of line.matchAll(pattern)) {
15
+ if (marker === 'private-pr-number') {
16
+ const start = match.index;
17
+ const end = start + match[0].length;
18
+ if ((start > 0 && /[0-9a-f]/i.test(line[start - 1]!)) ||
19
+ (end < line.length && /[0-9a-f]/i.test(line[end]!)) ||
20
+ /^#[0-9a-f]{6}$/i.test(match[0])) continue;
21
+ }
22
+ hits.push({ file: file.path, line: index + 1, marker });
23
+ }
24
+ }
25
+ }
26
+ }
27
+ return hits;
28
+ }
@@ -5,6 +5,7 @@ import { gunzipSync } from 'node:zlib';
5
5
  import { parse as parseYaml } from 'yaml';
6
6
  import { debug } from '../debug/log.js';
7
7
  import { packDirDeterministic } from './tgz.js';
8
+ import { scanPackInternalRefs, type InternalRefHit } from './pack-internal-refs.js';
8
9
  import { signIndex, verifyIndex, type MarketplaceIndex } from './signed-index.js';
9
10
 
10
11
  type IndexConnector = MarketplaceIndex['plugins'][number]['ai.elanous']['connectors'][number];
@@ -62,11 +63,35 @@ function unpackLocal(archive: Uint8Array, destination: string): void {
62
63
  }
63
64
  }
64
65
 
66
+ function scanArchiveInternalRefs(archive: Uint8Array): InternalRefHit[] {
67
+ const tar = gunzipSync(archive);
68
+ const field = (part: Buffer) => part.toString('utf8').replace(/\0.*$/, '');
69
+ const files: { path: string; text: string }[] = [];
70
+ for (let offset = 0; offset + 512 <= tar.length;) {
71
+ const header = tar.subarray(offset, offset + 512);
72
+ if (header.every(byte => byte === 0)) break;
73
+ const size = parseInt(field(header.subarray(124, 136)).trim(), 8);
74
+ const name = field(header.subarray(0, 100));
75
+ const prefix = field(header.subarray(345, 500));
76
+ const path = prefix ? `${prefix}/${name}` : name;
77
+ const contents = tar.subarray(offset + 512, offset + 512 + size);
78
+ // Recognized text formats remain scannable even if they contain a NUL byte.
79
+ const textFile = /(?:^|\/)(?:README|LICENSE|Dockerfile)(?:\.[^/]*)?$|\.(?:md|mdx|txt|json|jsonc|ya?ml|toml|xml|html?|css|scss|svg|mmd|sh|bash|py|js|jsx|mjs|cjs|ts|tsx|mts|cts|sql|csv)$/i.test(path);
80
+ if (textFile || !contents.includes(0)) {
81
+ // A known text format is decoded leniently — one invalid byte must not hide the rest of the file from the scan.
82
+ try { files.push({ path, text: new TextDecoder('utf-8', { fatal: !textFile }).decode(contents) }); }
83
+ catch { /* Binary files are not scanned as text. */ }
84
+ }
85
+ offset += 512 + Math.ceil(size / 512) * 512;
86
+ }
87
+ return scanPackInternalRefs(files);
88
+ }
89
+
65
90
  export interface PublishResult {
66
91
  ok: boolean;
67
92
  sequence: number;
68
93
  published: Array<{ name: string; version: string; sha256: string; bundled: string[] }>;
69
- skipped: Array<{ dir: string; reason: string }>;
94
+ skipped: Array<{ dir: string; reason: string; hits?: InternalRefHit[] }>;
70
95
  /** Published anyway, but a reviewer should look (e.g. a third-party graph relying on core recipes). */
71
96
  warnings: Array<{ dir: string; graph: string; reason: 'third-party-core-recipe'; recipes: string[] }>;
72
97
  }
@@ -128,8 +153,8 @@ export function publishMarket(input: {
128
153
  currentName = dir;
129
154
  currentVersion = undefined;
130
155
  const manifestPath = join(folder, 'plugin.json');
131
- const skip = (reason: string) => {
132
- skipped.push({ dir, reason });
156
+ const skip = (reason: string, hits?: InternalRefHit[]) => {
157
+ skipped.push({ dir, reason, ...(hits ? { hits } : {}) });
133
158
  debug.log('market.publish', 'skipped', { name: dir, version: currentVersion, sequence, reason });
134
159
  };
135
160
  if (!existsSync(manifestPath)) { skip('missing-plugin.json'); continue; }
@@ -245,6 +270,8 @@ export function publishMarket(input: {
245
270
  if (message.startsWith('bundle-conflict')) { skip('bundle-conflict'); continue; }
246
271
  throw error;
247
272
  }
273
+ const hits = scanArchiveInternalRefs(archive);
274
+ if (hits.length) { skip('internal-reference', hits.slice(0, 20)); continue; }
248
275
  let sha256 = createHash('sha256').update(archive).digest('hex');
249
276
  const recorded = recordedHashes.get(JSON.stringify([manifest.name, manifest.version]));
250
277
  if (recorded && recorded !== sha256) {
@@ -318,7 +345,7 @@ export function publishMarket(input: {
318
345
  signatureText: readFileSync(join(input.outDir, 'index.sig'), 'utf8'), trustedKeys });
319
346
  if (!verified.ok) throw new Error(`index verification failed: ${verified.reason}: ${verified.detail}`);
320
347
  for (const item of published) debug.log('market.publish', 'published', { name: item.name, version: item.version, sequence });
321
- return { ok: true, sequence, published, skipped, warnings };
348
+ return { ok: !skipped.some(item => item.reason === 'internal-reference'), sequence, published, skipped, warnings };
322
349
  } catch (error) {
323
350
  const rawReason = error instanceof Error ? error.message : 'unknown-error';
324
351
  const reason = input.key.privateKeyPem && rawReason.includes(input.key.privateKeyPem) ? 'invalid signing key' : rawReason;
@@ -0,0 +1,65 @@
1
+ export type Maturity = 'stable' | 'beta' | 'tool' | 'ops' | 'broken' | 'system';
2
+ export type Surface = 'pwa' | 'desktop' | 'ios' | 'android' | 'tui' | 'cli' | 'telegram' | 'discord' | 'acp';
3
+ export type Role = 'owner' | 'contributor' | 'general';
4
+
5
+ type SurfaceMaturity = { pwa: Maturity } & Partial<Record<Exclude<Surface, 'pwa'>, Maturity>>;
6
+
7
+ /** App Router pages, including the generated 404 and the dynamic missions route. */
8
+ export const FEATURE_MATURITY = {
9
+ pwaRoute: {
10
+ '/': { pwa: 'stable' },
11
+ '/404': { pwa: 'stable' },
12
+ '/setup': { pwa: 'stable' },
13
+ '/setup/done': { pwa: 'stable' },
14
+ '/consult': { pwa: 'stable' },
15
+ '/chat': { pwa: 'stable' },
16
+ '/term': { pwa: 'stable' },
17
+ '/live': { pwa: 'stable' },
18
+ '/market': { pwa: 'stable' },
19
+ '/sessions': { pwa: 'stable' },
20
+ '/settings': { pwa: 'beta' },
21
+ '/editor': { pwa: 'beta' },
22
+ '/intake': { pwa: 'beta' },
23
+ '/trace': { pwa: 'beta' },
24
+ '/board': { pwa: 'beta' },
25
+ '/autopilot': { pwa: 'beta' },
26
+ '/missions/[id]': { pwa: 'beta' },
27
+ '/tasks': { pwa: 'beta' },
28
+ '/workspace': { pwa: 'beta' },
29
+ '/reflection': { pwa: 'beta' },
30
+ '/vault': { pwa: 'beta' },
31
+ '/observatory': { pwa: 'beta' },
32
+ '/exec': { pwa: 'beta' },
33
+ '/field': { pwa: 'beta' },
34
+ '/showroom': { pwa: 'beta' },
35
+ '/workflows': { pwa: 'beta' },
36
+ '/approvals': { pwa: 'tool' },
37
+ '/worktrees': { pwa: 'tool' },
38
+ '/design-check': { pwa: 'tool' },
39
+ '/control': { pwa: 'tool' },
40
+ '/scheduler': { pwa: 'ops' },
41
+ '/dashboard': { pwa: 'ops' },
42
+ '/bots': { pwa: 'ops' },
43
+ '/botlab': { pwa: 'ops' },
44
+ '/morning': { pwa: 'broken' },
45
+ '/settings/devices': { pwa: 'broken' },
46
+ '/workflows/chat-ui': { pwa: 'broken' },
47
+ '/share': { pwa: 'system' },
48
+ },
49
+ } as const satisfies { pwaRoute: Record<string, SurfaceMaturity> };
50
+
51
+ /** Unregistered routes and surfaces are not implicitly mature. Desktop shares the PWA implementation. */
52
+ export function maturityOn(route: string, surface: Surface): Maturity | undefined {
53
+ if (!Object.prototype.hasOwnProperty.call(FEATURE_MATURITY.pwaRoute, route)) return undefined;
54
+ const grades: SurfaceMaturity = FEATURE_MATURITY.pwaRoute[route as keyof typeof FEATURE_MATURITY.pwaRoute];
55
+ return grades[surface] ?? (surface === 'desktop' ? grades.pwa : undefined);
56
+ }
57
+
58
+ export function visibleOn(route: string, surface: Surface, role: Role): boolean {
59
+ const maturity = maturityOn(route, surface);
60
+ if (!maturity) return false;
61
+ if (role === 'owner') return true;
62
+ if (maturity === 'system') return false;
63
+ if (route === '/settings') return true;
64
+ return maturity === 'stable' || (role === 'contributor' && (maturity === 'beta' || maturity === 'tool'));
65
+ }
@@ -0,0 +1,24 @@
1
+ import type { UserConfig } from '../user-config.js';
2
+ import { resolveLlmTier, type LlmTierSource } from './tier-resolver.js';
3
+ import type { ModelTier } from './types.js';
4
+ import type { ReasoningLevel } from '../user-config.js';
5
+
6
+ export type TurnReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
7
+ const REASONING_EFFORT: Record<ReasoningLevel, TurnReasoningEffort> = { off: 'minimal', low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh' };
8
+
9
+ /** Only a person's explicit LLM choice changes a chat turn's model. */
10
+ export function explicitTurnTier(
11
+ cfg: UserConfig,
12
+ sessionId: string,
13
+ ): { model: string; tier: ModelTier; source: LlmTierSource; reasoningEffort?: TurnReasoningEffort } | undefined {
14
+ try {
15
+ const resolved = resolveLlmTier(cfg.modelTier, cfg.llm.provider, { sessionId });
16
+ if (resolved.source !== 'session-override' && resolved.source !== 'user-config-surface') return undefined;
17
+ // On codex the balanced·better·best tiers share one model and differ only in reasoning level (low·medium·high),
18
+ // so carrying the model alone left the slider a no-op there (10-01 live).
19
+ const reasoningEffort = resolved.reasoningLevel ? REASONING_EFFORT[resolved.reasoningLevel] : undefined;
20
+ return { model: resolved.model, tier: resolved.tier, source: resolved.source, ...(reasoningEffort ? { reasoningEffort } : {}) };
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
@@ -0,0 +1,149 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
5
+
6
+ export interface MessageEnvelope {
7
+ from: string;
8
+ to: string;
9
+ body: string;
10
+ kind?: string;
11
+ }
12
+
13
+ export interface Message extends MessageEnvelope {
14
+ id: number;
15
+ createdAt: string;
16
+ }
17
+
18
+ export interface UnreadRecipient {
19
+ recipient: string;
20
+ count: number;
21
+ }
22
+
23
+ const SEAT_ALIASES: Readonly<Record<string, string>> = {
24
+ S: 'OP', OP: 'OP', COO: 'OP',
25
+ T: 'MK', MK: 'MK', CMO: 'MK',
26
+ O: 'TC', TC: 'TC', CTO: 'TC',
27
+ F: 'UX', UX: 'UX', CXO: 'UX',
28
+ };
29
+
30
+ /** Normalize historic role names to the current seats; leave other valid seats usable. */
31
+ export function canonicalSeatId(value: string): string {
32
+ if (typeof value !== 'string') throw new TypeError('seat must be a string');
33
+ const seat = value.trim().toUpperCase();
34
+ if (!/^[A-Z][A-Z0-9_-]{0,63}$/.test(seat)) throw new Error('invalid seat ID');
35
+ return Object.hasOwn(SEAT_ALIASES, seat) ? SEAT_ALIASES[seat]! : seat;
36
+ }
37
+
38
+ /** Check untrusted input before opening the DB or executing a write. */
39
+ export function validateMessageEnvelope(input: unknown): MessageEnvelope {
40
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('message must be an object');
41
+ const record = input as Record<string, unknown>;
42
+ const from = canonicalSeatId(record.from as string);
43
+ const to = canonicalSeatId(record.to as string);
44
+ if (typeof record.body !== 'string' || !record.body.trim()) throw new Error('message body must be nonempty');
45
+ if (record.kind !== undefined && (typeof record.kind !== 'string' || !/^[a-z][a-z0-9_-]{0,63}$/.test(record.kind))) {
46
+ throw new Error('invalid message kind');
47
+ }
48
+ if (Object.keys(record).some(key => !['from', 'to', 'body', 'kind'].includes(key))) throw new Error('unknown message field');
49
+ return record.kind === undefined ? { from, to, body: record.body } : { from, to, body: record.body, kind: record.kind as string };
50
+ }
51
+
52
+ function validCursor(cursor: number): number {
53
+ if (!Number.isSafeInteger(cursor) || cursor < 0) throw new Error('cursor must be a nonnegative safe integer');
54
+ return cursor;
55
+ }
56
+
57
+ interface MessageRow {
58
+ id: number;
59
+ sender: string;
60
+ recipient: string;
61
+ body: string;
62
+ kind: string | null;
63
+ created_at: string;
64
+ }
65
+
66
+ function fromRow(row: MessageRow): Message {
67
+ return { id: row.id, from: row.sender, to: row.recipient, body: row.body,
68
+ ...(row.kind === null ? {} : { kind: row.kind }), createdAt: row.created_at };
69
+ }
70
+
71
+ /** SQLite owns both append order and per-recipient acknowledgement progress. */
72
+ export class MsgStore {
73
+ readonly db: Database;
74
+
75
+ constructor(path: string = defaultMsgStorePath()) {
76
+ if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
77
+ this.db = new Database(path);
78
+ this.db.exec('PRAGMA busy_timeout = 5000');
79
+ this.db.exec(`CREATE TABLE IF NOT EXISTS msg_messages (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ sender TEXT NOT NULL,
82
+ recipient TEXT NOT NULL,
83
+ body TEXT NOT NULL,
84
+ kind TEXT,
85
+ created_at TEXT NOT NULL
86
+ );
87
+ CREATE INDEX IF NOT EXISTS idx_msg_messages_recipient_id ON msg_messages(recipient, id);
88
+ CREATE TABLE IF NOT EXISTS msg_cursors (
89
+ recipient TEXT PRIMARY KEY,
90
+ last_id INTEGER NOT NULL DEFAULT 0 CHECK(last_id >= 0)
91
+ );`);
92
+ }
93
+
94
+ append(input: MessageEnvelope): Message {
95
+ const msg = validateMessageEnvelope(input);
96
+ const createdAt = new Date().toISOString();
97
+ const result = this.db.query(`INSERT INTO msg_messages(sender, recipient, body, kind, created_at)
98
+ VALUES (?, ?, ?, ?, ?)`).run(msg.from, msg.to, msg.body, msg.kind ?? null, createdAt);
99
+ return { id: Number(result.lastInsertRowid), ...msg, createdAt };
100
+ }
101
+
102
+ post(input: MessageEnvelope): Message { return this.append(input); }
103
+
104
+ /** Messages strictly after the supplied cursor, ordered by durable insertion ID. */
105
+ listByRecipient(recipient: string, after: number = 0, limit: number = 100): Message[] {
106
+ const to = canonicalSeatId(recipient);
107
+ validCursor(after);
108
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new Error('limit must be between 1 and 1000');
109
+ return (this.db.query(`SELECT id, sender, recipient, body, kind, created_at FROM msg_messages
110
+ WHERE recipient = ? AND id > ? ORDER BY id ASC LIMIT ?`).all(to, after, limit) as MessageRow[]).map(fromRow);
111
+ }
112
+
113
+ list(recipient: string, after: number = 0, limit: number = 100): Message[] {
114
+ return this.listByRecipient(recipient, after, limit);
115
+ }
116
+
117
+ getCursor(recipient: string): number {
118
+ const to = canonicalSeatId(recipient);
119
+ return (this.db.query('SELECT last_id FROM msg_cursors WHERE recipient = ?').get(to) as { last_id: number } | null)?.last_id ?? 0;
120
+ }
121
+
122
+ /** Atomically reject future IDs and keep concurrent or stale acknowledgements from rewinding. */
123
+ advanceCursor(recipient: string, cursor: number): number {
124
+ const to = canonicalSeatId(recipient);
125
+ validCursor(cursor);
126
+ const result = this.db.query(`INSERT INTO msg_cursors(recipient, last_id)
127
+ SELECT ?, ? WHERE ? <= (SELECT COALESCE(MAX(id), 0) FROM msg_messages)
128
+ ON CONFLICT(recipient) DO UPDATE SET last_id = MAX(msg_cursors.last_id, excluded.last_id)`)
129
+ .run(to, cursor, cursor);
130
+ if (result.changes === 0) throw new Error('cursor exceeds the latest message ID');
131
+ return this.getCursor(to);
132
+ }
133
+
134
+ ack(recipient: string, cursor: number): number { return this.advanceCursor(recipient, cursor); }
135
+
136
+ unreadRecipients(): UnreadRecipient[] {
137
+ return this.db.query(`SELECT m.recipient AS recipient, COUNT(*) AS count
138
+ FROM msg_messages m LEFT JOIN msg_cursors c ON c.recipient = m.recipient
139
+ WHERE m.id > COALESCE(c.last_id, 0)
140
+ GROUP BY m.recipient ORDER BY m.recipient ASC`).all() as UnreadRecipient[];
141
+ }
142
+
143
+ unread(): UnreadRecipient[] { return this.unreadRecipients(); }
144
+
145
+ close(): void { this.db.close(); }
146
+ }
147
+
148
+ export function defaultMsgStorePath(): string { return join(elanousStateRoot(), 'msg', 'messages.db'); }
149
+ export function openMsgStore(path?: string): MsgStore { return new MsgStore(path); }