elanous 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/package.json +5 -5
  178. package/plugins/card-followup/README.md +13 -4
  179. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  180. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  181. package/plugins/card-followup/graphs/run-step.ts +122 -18
  182. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  183. package/plugins/card-followup/plugin.json +1 -1
  184. package/plugins/doc-draft/README.md +13 -0
  185. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  186. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  187. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  188. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  189. package/plugins/doc-draft/plugin.json +24 -0
  190. package/plugins/geo-check/README.md +3 -1
  191. package/plugins/geo-check/graphs/run-step.ts +24 -3
  192. package/plugins/geo-check/plugin.json +1 -1
  193. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  194. package/plugins/job-coach/README.md +1 -1
  195. package/plugins/job-coach/graphs/run-step.ts +2 -2
  196. package/plugins/job-coach/plugin.json +1 -1
  197. package/plugins/job-coach/plugin.ts +1 -1
  198. package/scripts/affinity/endcard.sh +3 -2
  199. package/scripts/affinity/reframe.sh +3 -1
  200. package/scripts/ci-public-export-leak-gate.ts +48 -0
  201. package/scripts/codex-quota-alert.ts +122 -258
  202. package/scripts/coord-tracks.json +4 -4
  203. package/scripts/field-feed/deliver-node.ts +52 -0
  204. package/scripts/field-feed/feed-node.ts +67 -0
  205. package/scripts/field-feed/lib.ts +162 -0
  206. package/scripts/field-feed/reel-node.ts +24 -0
  207. package/scripts/field-feed/start.ts +62 -0
  208. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  209. package/scripts/install.sh +92 -13
  210. package/scripts/landing-heal/clean-checkout.ts +114 -0
  211. package/scripts/landing-heal/run-mission.ts +38 -3
  212. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  213. package/scripts/public-export.ts +8 -4
  214. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  215. package/scripts/release-loop/env-known-failures.ts +31 -0
  216. package/scripts/release-loop/gate-node.ts +81 -19
  217. package/scripts/release-loop/known-issues-node.ts +159 -0
  218. package/scripts/release-loop/npm-publish-node.ts +118 -0
  219. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  220. package/scripts/release-loop/release-readiness.ts +58 -0
  221. package/scripts/release-loop/unattended-release.ts +93 -0
  222. package/scripts/ux/measure-first-text.ts +102 -0
  223. package/scripts/vflow-collect.ts +20 -7
  224. package/scripts/vflow-search.ts +73 -0
  225. package/scripts/video-free-line-test-walker.ts +36 -0
  226. package/scripts/video-pipeline.ts +2 -2
  227. package/skills/explainer-video/SKILL.md +12 -9
  228. package/skills/explainer-video/engine/build.mjs +1 -1
  229. package/skills/explainer-video/engine/reel.mjs +138 -31
  230. package/skills/explainer-video/engine/reel.sh +2 -1
  231. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  232. package/skills/grill-me/SKILL.md +1 -1
  233. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  234. package/src/a2a/server.ts +2 -0
  235. package/src/acp/elanous-aware-peers.ts +30 -0
  236. package/src/acp/server.ts +91 -15
  237. package/src/agent/monad-agent-turn.ts +25 -6
  238. package/src/agent/shared-app-tools.ts +5 -3
  239. package/src/agent-substrate/review-intent.ts +6 -2
  240. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  241. package/src/auth/github-app-token.ts +4 -1
  242. package/src/autopilot/track-agent-shadow.ts +151 -0
  243. package/src/autopilot/track-agent.ts +23 -3
  244. package/src/boot/acp-server-help.ts +16 -0
  245. package/src/boot/daemon-prompt-turn.ts +31 -10
  246. package/src/boot/daemon-tools/index.ts +7 -1
  247. package/src/budget/codex-credit-pace.ts +84 -0
  248. package/src/budget/codex-credit-plan.ts +154 -0
  249. package/src/budget/codex-reset-credit-state.ts +2 -0
  250. package/src/chat/chat-cli.ts +4 -1
  251. package/src/chat/short-question.ts +40 -0
  252. package/src/cli/agent-cli.ts +24 -15
  253. package/src/cli/ask-bare.ts +42 -0
  254. package/src/cli/cli-user-error.ts +25 -0
  255. package/src/cli/codex-credit-plan-view.ts +25 -0
  256. package/src/cli/dependence-gauge.ts +237 -0
  257. package/src/cli/doctor-cli.ts +5 -0
  258. package/src/cli/doctor-modes.ts +48 -0
  259. package/src/cli/intake-cli.ts +12 -2
  260. package/src/cli/intake-source-cli.ts +58 -0
  261. package/src/cli/machine-cli.ts +23 -0
  262. package/src/cli/msg-cli.ts +137 -0
  263. package/src/cli/plugin-cli.ts +25 -0
  264. package/src/cli/pr-cli.ts +36 -0
  265. package/src/cli/provider-cli.ts +5 -0
  266. package/src/cli/release-cli.ts +42 -1
  267. package/src/cli/release-run-if-ready.ts +48 -0
  268. package/src/cli/setup-cli.ts +27 -6
  269. package/src/cli/setup-recommend.ts +8 -0
  270. package/src/cli/usage-cli.ts +11 -1
  271. package/src/codex/setup.ts +27 -8
  272. package/src/dashboard/index.ts +1 -1
  273. package/src/discord.ts +56 -6
  274. package/src/domains/core-tools.ts +37 -14
  275. package/src/domains/outbound-alert.ts +50 -11
  276. package/src/exec-requests/planner.ts +140 -0
  277. package/src/exec-requests/runner.ts +338 -0
  278. package/src/exec-requests/store.ts +76 -0
  279. package/src/field/field-media.ts +8 -3
  280. package/src/field/field-reel.ts +196 -0
  281. package/src/grok/credential.ts +7 -5
  282. package/src/harness/aside-run-status.ts +33 -0
  283. package/src/harness/browser-verify.ts +48 -17
  284. package/src/harness/harness-cli-command.ts +25 -3
  285. package/src/harness/image-load-state.ts +21 -0
  286. package/src/harness/process-garbage.ts +45 -0
  287. package/src/index.ts +86 -14
  288. package/src/instance/leader.ts +13 -1
  289. package/src/intake-plane/collect-github.ts +5 -3
  290. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  291. package/src/intake-plane/discord-seat-work.ts +56 -0
  292. package/src/intake-plane/intake-sources.ts +202 -0
  293. package/src/intake-plane/items.ts +28 -12
  294. package/src/intake-plane/seat-doc-route.ts +207 -0
  295. package/src/intake-plane/submit-intake-work.ts +9 -1
  296. package/src/intake-plane/telegram-seat-work.ts +72 -0
  297. package/src/llm/model-defaults.ts +24 -1
  298. package/src/llm.ts +5 -4
  299. package/src/machines/machine-ledger.ts +127 -0
  300. package/src/market/pack-internal-refs.ts +28 -0
  301. package/src/market/publish.ts +31 -4
  302. package/src/maturity/feature-maturity.ts +65 -0
  303. package/src/model-tier/turn-tier.ts +24 -0
  304. package/src/msg/msg-store.ts +149 -0
  305. package/src/native-tool-catalog.ts +2 -1
  306. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  307. package/src/nexus/api/consult-requests.ts +117 -0
  308. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  309. package/src/nexus/api/exec-requests.ts +51 -0
  310. package/src/nexus/api/field-uploads.ts +40 -3
  311. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  312. package/src/nexus/api/graph-approvals.ts +71 -4
  313. package/src/nexus/api/http-server.ts +61 -7
  314. package/src/nexus/api/seat-requests.ts +254 -0
  315. package/src/nexus/api/trace-run-scan.ts +37 -0
  316. package/src/nexus/api/trace.ts +19 -3
  317. package/src/nexus/index.ts +3 -0
  318. package/src/nexus/setup-status.ts +4 -5
  319. package/src/notify/llm-budget-alert.ts +16 -10
  320. package/src/oauth/codex-account-rotation.ts +26 -4
  321. package/src/oauth/codex-account-store.ts +20 -2
  322. package/src/oauth/codex-browser-login.ts +173 -0
  323. package/src/oauth/codex.ts +4 -1
  324. package/src/oauth/fallback-chain.ts +5 -5
  325. package/src/oauth/store.ts +42 -5
  326. package/src/onboarding/continue-here.ts +29 -0
  327. package/src/onboarding/entry-hints.ts +8 -0
  328. package/src/onboarding/full-screen-io.ts +35 -3
  329. package/src/onboarding/help/skills.md +1 -1
  330. package/src/onboarding/non-interactive.ts +1 -0
  331. package/src/onboarding/subscription-hint.ts +27 -0
  332. package/src/onboarding/web-first.ts +83 -0
  333. package/src/onboarding.ts +230 -23
  334. package/src/plugins/maker/node-install.ts +92 -0
  335. package/src/plugins/maker/node-maker.ts +139 -0
  336. package/src/plugins/maker/plugin-maker.ts +19 -7
  337. package/src/policy/llm-policy.ts +268 -0
  338. package/src/prompt-library/session-guidance.ts +7 -1
  339. package/src/prompt-library/universal-preamble.ts +2 -2
  340. package/src/relay/relay-cli.ts +20 -0
  341. package/src/relay/relay-server.ts +220 -0
  342. package/src/release-loop/checklist.ts +9 -8
  343. package/src/self-dev/decompose.ts +1 -0
  344. package/src/self-dev/dev-pipeline.ts +6 -3
  345. package/src/self-dev/run-record-mirror.ts +44 -0
  346. package/src/self-dev/run-store.ts +12 -3
  347. package/src/self-implement/harness-policy-gates.ts +4 -1
  348. package/src/self-implement/report-origin.ts +12 -1
  349. package/src/session/chat.ts +17 -5
  350. package/src/skills/shared-agent-skill-roots.ts +25 -0
  351. package/src/steward/failure-streak.ts +78 -0
  352. package/src/steward/steward-cards.ts +24 -0
  353. package/src/steward/triage-budget.ts +127 -0
  354. package/src/steward/triage-plan.ts +75 -0
  355. package/src/steward/triage.ts +244 -42
  356. package/src/task-cards/card-store.ts +1 -1
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  358. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  359. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  360. package/src/telegram.ts +38 -2
  361. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  362. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  363. package/src/user-config.ts +80 -4
  364. package/src/version/packed-revision.json +1 -1
  365. package/src/video-pipeline/recipes/character.ts +42 -5
  366. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  367. package/src/video-pipeline/recipes/free-line.ts +16 -3
  368. package/src/video-pipeline/recipes/remote.ts +7 -1
  369. package/src/video-pipeline/vflow-reference.ts +109 -0
  370. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  379. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  448. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  449. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  450. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -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 =>
@@ -0,0 +1,25 @@
1
+ import { statSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ export interface SharedAgentSkillRootsOptions {
6
+ home?: string;
7
+ /** Directory-existence probe; defaults to a read-only filesystem stat. */
8
+ exists?: (dir: string) => boolean;
9
+ includeSharedAgentSkills?: boolean;
10
+ }
11
+
12
+ /** Discover the shared user skill root without creating or modifying it. */
13
+ export function sharedAgentSkillRoots({
14
+ home = homedir(),
15
+ exists = (dir: string) => statSync(dir).isDirectory(),
16
+ includeSharedAgentSkills = true,
17
+ }: SharedAgentSkillRootsOptions): string[] {
18
+ if (!includeSharedAgentSkills) return [];
19
+ const root = join(home, '.agents', 'skills');
20
+ try {
21
+ return exists(root) ? [root] : [];
22
+ } catch {
23
+ return [];
24
+ }
25
+ }
@@ -0,0 +1,78 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { redactSecretText } from '../debug/log.js';
4
+
5
+ export type StewardStage = 'sync' | 'triage' | 'schedule' | 'report';
6
+ export interface FailureStreak {
7
+ consecutive: number;
8
+ lastStage: StewardStage;
9
+ lastReason: string;
10
+ lastAt: string;
11
+ alertedAt?: string;
12
+ open?: { stage: StewardStage; startedAt: string };
13
+ }
14
+
15
+ const stages: readonly string[] = ['sync', 'triage', 'schedule', 'report'];
16
+ const empty = (): FailureStreak => ({ consecutive: 0, lastStage: 'sync', lastReason: '', lastAt: '' });
17
+ const pathFor = (root: string): string => join(root, 'steward', 'streak.json');
18
+
19
+ export function readFailureStreak(root: string): FailureStreak {
20
+ try {
21
+ const value: unknown = JSON.parse(readFileSync(pathFor(root), 'utf8'));
22
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return empty();
23
+ const s = value as Record<string, unknown>;
24
+ if (!Number.isSafeInteger(s.consecutive) || (s.consecutive as number) < 0 ||
25
+ !stages.includes(s.lastStage as string) || typeof s.lastReason !== 'string' || typeof s.lastAt !== 'string' ||
26
+ (s.alertedAt !== undefined && typeof s.alertedAt !== 'string')) return empty();
27
+ if (s.open !== undefined && (!s.open || typeof s.open !== 'object' || Array.isArray(s.open) ||
28
+ !stages.includes((s.open as Record<string, unknown>).stage as string) ||
29
+ typeof (s.open as Record<string, unknown>).startedAt !== 'string')) return empty();
30
+ return s as unknown as FailureStreak;
31
+ } catch { return empty(); }
32
+ }
33
+
34
+ export function writeFailureStreak(root: string, state: FailureStreak): void {
35
+ const dir = join(root, 'steward');
36
+ mkdirSync(dir, { recursive: true });
37
+ const path = pathFor(root);
38
+ const tmp = `${path}.${process.pid}.tmp`;
39
+ writeFileSync(tmp, JSON.stringify(state));
40
+ renameSync(tmp, path);
41
+ }
42
+
43
+ export function markStageStart(root: string, stage: StewardStage, now: Date = new Date()): FailureStreak {
44
+ const state = readFailureStreak(root);
45
+ if (state.open) {
46
+ state.consecutive += 1;
47
+ state.lastStage = state.open.stage;
48
+ state.lastReason = `killed or timed out: ${state.open.stage}`;
49
+ state.lastAt = now.toISOString();
50
+ }
51
+ state.open = { stage, startedAt: now.toISOString() };
52
+ writeFailureStreak(root, state);
53
+ return state;
54
+ }
55
+
56
+ export function markStageEnd(root: string, stage: StewardStage, ok: boolean, reason = '', now: Date = new Date()): FailureStreak {
57
+ const state = readFailureStreak(root);
58
+ delete state.open;
59
+ if (ok) {
60
+ state.consecutive = 0;
61
+ delete state.alertedAt;
62
+ } else {
63
+ state.consecutive += 1;
64
+ }
65
+ state.lastStage = stage;
66
+ state.lastReason = ok ? '' : redactSecretText(reason);
67
+ state.lastAt = now.toISOString();
68
+ writeFailureStreak(root, state);
69
+ return state;
70
+ }
71
+
72
+ export function shouldAlert(state: FailureStreak, threshold: number): boolean {
73
+ return state.consecutive >= threshold && !state.alertedAt;
74
+ }
75
+
76
+ export function failureAlertText(state: FailureStreak): string {
77
+ return `스튜어드 연속 실패 ${state.consecutive}회 · 마지막 ${state.lastStage} · ${redactSecretText(state.lastReason).slice(0, 120)} · 로그 /tmp/elanous-loop-steward.log`;
78
+ }
@@ -43,3 +43,27 @@ export function recordTriageOnCards(decisions: ScheduledDecision[], issues: Tria
43
43
  if (!deps.store) store.close();
44
44
  }
45
45
  }
46
+
47
+ function recordOnCard(section: 'launch' | 'outcome', issue: Pick<TriageIssue, 'identifier' | 'title'>, value: object, deps: StewardCardDeps): void {
48
+ const store = deps.store ?? new CardStore(deps.root);
49
+ try {
50
+ const content = redactSecrets(JSON.stringify(value));
51
+ const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
52
+ const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
53
+ if (!card.sections.some(item => item.key === key)) {
54
+ store.appendSection(card.id, { key, owner: 'steward', content });
55
+ }
56
+ } finally {
57
+ if (!deps.store) store.close();
58
+ }
59
+ }
60
+
61
+ /** Append a launch revision to the issue card; an identical retry keeps its original section. */
62
+ export function recordLaunchOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, launch: object, deps: StewardCardDeps = {}): void {
63
+ recordOnCard('launch', issue, launch, deps);
64
+ }
65
+
66
+ /** Append an outcome revision to the issue card without replacing earlier sections. */
67
+ export function recordOutcomeOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, outcome: object, deps: StewardCardDeps = {}): void {
68
+ recordOnCard('outcome', issue, outcome, deps);
69
+ }
@@ -0,0 +1,127 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { debug } from '../debug/log.js';
3
+ import type { TriageDecision, TriageIssue } from './triage.js';
4
+
5
+ export interface HashedTriageDecision extends TriageDecision {
6
+ inputHash: string;
7
+ /** Other issues whose ref/body the judge read while producing this decision. */
8
+ contextInputs?: string[];
9
+ /** Retained from a previous run without a current judgment; never route as a fresh decision. */
10
+ deferred?: true;
11
+ }
12
+
13
+ export function triageInputHash(issue: TriageIssue, issues: TriageIssue[] = [], tracks?: Record<string, string>, contextInputs: string[] = []): string {
14
+ const byId = new Map(issues.map(item => [item.identifier, item]));
15
+ return createHash('sha256').update(JSON.stringify([
16
+ 'context-inputs-v2', issue.identifier, issue.ref, issue.title, issue.body,
17
+ issues.map(item => [item.identifier, item.title]), tracks && Object.keys(tracks).length ? tracks : null,
18
+ contextInputs.map(id => [id, byId.get(id)?.ref ?? null, byId.get(id)?.body ?? null]),
19
+ ])).digest('hex');
20
+ }
21
+
22
+ export async function triageWithinBudget({ issues, previous, judge, tracks, concurrency = 8, perIssueMs = 90_000, deadlineMs = 240_000, now = Date.now, onJudged }: {
23
+ issues: TriageIssue[];
24
+ previous: Array<TriageDecision & { inputHash?: string }>;
25
+ judge: (issue: TriageIssue, observedIssues?: TriageIssue[]) => Promise<TriageDecision>;
26
+ tracks?: Record<string, string>;
27
+ concurrency?: number;
28
+ perIssueMs?: number;
29
+ deadlineMs?: number;
30
+ now?: () => number;
31
+ onJudged?: (decision: HashedTriageDecision) => void;
32
+ }): Promise<{ decisions: HashedTriageDecision[]; stats: { reused: number; judged: number; timedOut: number; deferred: number } }> {
33
+ const end = now() + deadlineMs;
34
+ const slots = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 4;
35
+ const stats = { reused: 0, judged: 0, timedOut: 0, deferred: 0 };
36
+ const old = new Map(previous.map(decision => [decision.issue, decision]));
37
+ const isTemporary = (decision: TriageDecision): boolean =>
38
+ decision.rung === 'hitl' && decision.hitlReason === 'other' &&
39
+ ['triage deferred — deadline', 'triage judgment timed out — human review',
40
+ 'triage judgment unavailable — human review'].includes(decision.why);
41
+ const decisions: HashedTriageDecision[] = new Array(issues.length);
42
+ const pending: number[] = [];
43
+ for (const [index, issue] of issues.entries()) {
44
+ const earlier = old.get(issue.identifier) as HashedTriageDecision | undefined;
45
+ const hash = triageInputHash(issue, issues, tracks, earlier?.contextInputs ?? []);
46
+ if (earlier?.inputHash === hash && !earlier.deferred && !isTemporary(earlier)) {
47
+ decisions[index] = { ...earlier, inputHash: hash };
48
+ stats.reused++;
49
+ } else pending.push(index);
50
+ }
51
+
52
+ let cursor = 0;
53
+ const activeWork = new Set<Promise<TriageDecision>>();
54
+ const settleSignals = new Set<Promise<boolean>>();
55
+ const timedOut = Symbol('triage-timeout');
56
+ const worker = async (): Promise<void> => {
57
+ while (cursor < pending.length) {
58
+ const remaining = end - now();
59
+ if (remaining <= 0) return;
60
+ if (activeWork.size >= slots) {
61
+ let timer: ReturnType<typeof setTimeout> | undefined;
62
+ const settled = Promise.race([...settleSignals]);
63
+ let deadlineElapsed: boolean;
64
+ try {
65
+ deadlineElapsed = await Promise.race([settled, new Promise<true>(resolve => { timer = setTimeout(() => resolve(true), remaining); })]);
66
+ } finally { if (timer) clearTimeout(timer); }
67
+ if (deadlineElapsed) return;
68
+ continue;
69
+ }
70
+ const index = pending[cursor++]!;
71
+ const issue = issues[index]!;
72
+ const observed = new Set<string>();
73
+ const observedIssues = issues.map(item => new Proxy(item, {
74
+ get(target, property, receiver) {
75
+ if (item.identifier !== issue.identifier && (property === 'ref' || property === 'body')) observed.add(item.identifier);
76
+ return Reflect.get(target, property, receiver);
77
+ },
78
+ }));
79
+ // Check immediately before invoking; a clock that advanced while preparing inputs must not start a new call.
80
+ const issueRemaining = end - now();
81
+ if (issueRemaining <= 0) { cursor--; return; }
82
+ let timer: ReturnType<typeof setTimeout> | undefined;
83
+ let decision: TriageDecision;
84
+ try {
85
+ const work = Promise.resolve(judge(issue, observedIssues));
86
+ // Even a timed-out judge keeps its concurrency slot until the underlying call settles.
87
+ activeWork.add(work);
88
+ const settled = work.then(() => false, () => false);
89
+ void settled.then(() => {
90
+ activeWork.delete(work);
91
+ settleSignals.delete(settled);
92
+ });
93
+ settleSignals.add(settled);
94
+ const timeout = new Promise<never>((_, reject) => {
95
+ timer = setTimeout(() => reject(timedOut), Math.min(perIssueMs, issueRemaining));
96
+ });
97
+ decision = await Promise.race([work, timeout]);
98
+ stats.judged++;
99
+ } catch (error) {
100
+ if (error === timedOut) {
101
+ stats.timedOut++;
102
+ decision = { issue: issue.identifier, rung: 'hitl', hitlReason: 'other', dependsOn: [], priority: 0, why: 'triage judgment timed out — human review' };
103
+ } else {
104
+ stats.judged++;
105
+ decision = { issue: issue.identifier, rung: 'hitl', hitlReason: 'other', dependsOn: [], priority: 0, why: 'triage judgment unavailable — human review' };
106
+ }
107
+ } finally { if (timer) clearTimeout(timer); }
108
+ const contextInputs = [...observed].sort();
109
+ decisions[index] = { ...decision, inputHash: triageInputHash(issue, issues, tracks, contextInputs),
110
+ ...(contextInputs.length ? { contextInputs } : {}) };
111
+ onJudged?.(decisions[index]!);
112
+ }
113
+ };
114
+ await Promise.all(Array.from({ length: Math.min(slots, pending.length) }, () => worker()));
115
+ for (const index of pending.slice(cursor)) {
116
+ const issue = issues[index]!;
117
+ const earlier = old.get(issue.identifier);
118
+ decisions[index] = earlier
119
+ ? { ...earlier, inputHash: earlier.inputHash ?? '', deferred: true }
120
+ : { issue: issue.identifier, rung: 'hitl', hitlReason: 'other', dependsOn: [], priority: 0,
121
+ why: 'triage deferred — deadline', inputHash: triageInputHash(issue, issues, tracks), deferred: true };
122
+ if (!earlier) onJudged?.(decisions[index]!);
123
+ stats.deferred++;
124
+ }
125
+ debug.log('steward.triage', 'budget', stats);
126
+ return { decisions, stats };
127
+ }
@@ -0,0 +1,75 @@
1
+ import { resolveRoleModel } from '../user-config.js';
2
+ import type { HitlReason, Rung, TriageIssue } from './triage.js';
3
+
4
+ export type StewardAsk = (prompt: string, role: 'classify' | 'planning') => Promise<unknown>;
5
+ export interface ClassifiedIssue { issue: string; rung: Rung; why: string; hitlReason?: HitlReason; duplicateOf?: string }
6
+ export interface IssuePlan { issue: string; priority: number; dependsOn: string[]; owner?: string }
7
+
8
+ const unsafe: Array<[HitlReason, RegExp]> = [
9
+ ['money', /돈|유료|결제|구매|판매|송금|환불|크레딧\s*(?:추가|구매)|paid|payment|purchase/i],
10
+ ['public', /공개|발행|외부\s*게시|publish|public\s*release/i],
11
+ ['security', /보안|(?:API|보안|인증|접근)\s*키|(?:키|자격|권한|secret|credential|permission)\s*(?:변경|교체|발급|삭제|공유|노출)|키체인|\b(?:api[ -]?key|secret[ -]?key|access[ -]?token)\b/i],
12
+ ['irreversible', /비가역|강제\s*푸시|force.push|영구\s*삭제|리셋권\s*소비|drop\s*table|초기화/i],
13
+ ];
14
+
15
+ export function unsafeReason(issue: TriageIssue): HitlReason | undefined {
16
+ return unsafe.find(([, pattern]) => pattern.test(`${issue.title}\n${issue.body}`))?.[0];
17
+ }
18
+
19
+ function parsed(raw: unknown): Record<string, unknown> {
20
+ const value: unknown = typeof raw === 'string'
21
+ ? JSON.parse(raw.replace(/^```(?:json)?\s*|\s*```$/g, '').trim()) : raw;
22
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid steward response');
23
+ return value as Record<string, unknown>;
24
+ }
25
+
26
+ export function ruleJudgment(issue: TriageIssue, issues: TriageIssue[]): ClassifiedIssue | undefined {
27
+ const normalize = (text: string) => text.normalize('NFKC').trim().replace(/\s+/g, ' ').toLocaleLowerCase();
28
+ const title = normalize(issue.title);
29
+ const index = issues.findIndex(item => item.identifier === issue.identifier);
30
+ const prior = index < 0 ? issues.filter(item => item.identifier !== issue.identifier) : issues.slice(0, index);
31
+ // Same title alone is not proof: two different tasks can share a title. Only same title AND same body is a
32
+ // rule duplicate; anything else goes to classification.
33
+ const body = normalize(issue.body ?? '');
34
+ const duplicate = title && prior.find(item => normalize(item.title) === title && normalize(item.body ?? '') === body);
35
+ if (duplicate) return { issue: issue.identifier, rung: 0, duplicateOf: duplicate.identifier, why: `duplicate of ${duplicate.identifier}` };
36
+ return undefined;
37
+ }
38
+
39
+ export async function askSteward(prompt: string, role: 'classify' | 'planning'): Promise<unknown> {
40
+ const { streamLLM } = await import('../llm.js');
41
+ const result = await streamLLM([{ role: 'user', content: prompt }], () => {}, { model: resolveRoleModel(role).model });
42
+ return parsed(result);
43
+ }
44
+
45
+ export async function classifyIssue(issue: TriageIssue, ask: StewardAsk = askSteward): Promise<ClassifiedIssue> {
46
+ const candidate = unsafeReason(issue);
47
+ const response = parsed(await ask(`스튜어드 분류. JSON 객체만: {"rung":0|1|2|3|4|5|"hitl","hitlReason":null,"why":"한 문장"}. 0=이미됨, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl; 그 외 사람 확인은 other. 확인되지 않은 완료를 추측하지 마라. ${candidate ? `안전 후보 ${candidate}: 이유를 확인하라.` : ''}\n이슈: ${JSON.stringify(issue)}`, 'classify'));
48
+ if (![0, 1, 2, 3, 4, 5, 'hitl'].includes(response.rung as Rung) || typeof response.why !== 'string' || !response.why.trim()) throw new Error(`Invalid steward classification: ${issue.identifier}`);
49
+ const reason = candidate ?? (unsafe.some(([name]) => name === response.hitlReason) ? response.hitlReason as HitlReason : undefined)
50
+ ?? (response.rung === 'hitl' ? 'other' : undefined);
51
+ return { issue: issue.identifier, rung: reason ? 'hitl' : response.rung as Rung,
52
+ ...(reason ? { hitlReason: reason } : {}), why: response.why.trim().replace(/\s+/g, ' ') };
53
+ }
54
+
55
+ /** One request for the current batch, never one request per issue. */
56
+ export async function planIssues(classified: ClassifiedIssue[], ask: StewardAsk = askSteward, tracks?: Record<string, string>, issues: TriageIssue[] = []): Promise<IssuePlan[]> {
57
+ if (!classified.length) return [];
58
+ const keys = Object.keys(tracks ?? {});
59
+ const byId = new Map(issues.map(issue => [issue.identifier, issue]));
60
+ const response = parsed(await ask(`스튜어드 계획. 새로 분류한 이슈마다 priority(숫자), dependsOn(제공된 이슈 키 배열), owner(담당 트랙 키 또는 null)를 정해 JSON {"plans":[{"issue":"키","priority":1,"dependsOn":[],"owner":null}]} 만 출력. 새로 분류한 이슈만 한 번씩 포함하라. 다른 키를 의존으로 넣지 마라. 담당 트랙: ${JSON.stringify(tracks ?? {})}\n제공된 이슈 키: ${JSON.stringify(issues.map(row => ({ key: row.identifier, title: row.title })))}\n새로 분류한 이슈: ${JSON.stringify(classified.map(row => ({ ...row, title: byId.get(row.issue)?.title ?? '', body: byId.get(row.issue)?.body ?? '' })))}`, 'planning'));
61
+ const plans = response.plans;
62
+ if (!Array.isArray(plans) || plans.length !== classified.length) throw new Error('Invalid steward plan');
63
+ const allowed = new Set(issues.length ? issues.map(row => row.identifier) : classified.map(row => row.issue));
64
+ const planned = new Set(classified.map(row => row.issue));
65
+ const seen = new Set<string>();
66
+ return plans.map((raw: unknown) => {
67
+ const row = parsed(raw);
68
+ if (typeof row.issue !== 'string' || !planned.has(row.issue) || seen.has(row.issue) ||
69
+ typeof row.priority !== 'number' || !Number.isFinite(row.priority) ||
70
+ !Array.isArray(row.dependsOn) || !row.dependsOn.every((dep: unknown) => typeof dep === 'string' && allowed.has(dep) && dep !== row.issue)) throw new Error('Invalid steward plan');
71
+ seen.add(row.issue);
72
+ return { issue: row.issue, priority: row.priority, dependsOn: row.dependsOn as string[],
73
+ ...(typeof row.owner === 'string' && keys.includes(row.owner) ? { owner: row.owner } : {}) };
74
+ });
75
+ }