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
@@ -2,7 +2,7 @@
2
2
  import { INSTALLED_PACKAGE_MARKERS, isInstalledPackagePath } from './installed-package.js';
3
3
  import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
- import { dirname, join, resolve } from 'node:path';
5
+ import { dirname, join, resolve, basename } from 'node:path';
6
6
 
7
7
  export interface LeaderRefusalRecord {
8
8
  refusedAt: string;
@@ -53,10 +53,22 @@ export function normalizeTree(p: string): string {
53
53
  try { return realpathSync(abs); } catch { return abs; }
54
54
  }
55
55
 
56
+ /** npm/Homebrew/nvm `<prefix>/lib/node_modules` · bun `…/install/global/node_modules` · pnpm `…/global/<n>/node_modules`. */
57
+ export function isGlobalNodeModules(dir: string): boolean {
58
+ if (basename(dir) !== 'node_modules') return false;
59
+ const parent = basename(dirname(dir));
60
+ return parent === 'lib' || parent === 'global' || (/^\d+$/.test(parent) && basename(dirname(dirname(dir))) === 'global');
61
+ }
62
+
56
63
  export function treeFromScriptPath(scriptPath: string): string | null {
57
64
  let dir = dirname(normalizeTree(scriptPath));
58
65
  for (let i = 0; i < 30; i++) {
59
66
  if (existsSync(join(dir, '.git'))) return dir;
67
+ // A GLOBAL install stops at its node_modules: a git repo around the install prefix (Homebrew's /opt/homebrew
68
+ // is one; so is a dotfiles-managed home) is not this code's source tree. Without this stop `npm i -g` under
69
+ // Homebrew ran as a «source tree» test universe writing config to /opt/homebrew/.elanous-test (OB3 · 10-01).
70
+ // A project-local <checkout>/node_modules/elanous still resolves to the checkout (the existing design).
71
+ if (isGlobalNodeModules(dir)) return null;
60
72
  const parent = dirname(dir);
61
73
  if (parent === dir) return null;
62
74
  dir = parent;
@@ -131,7 +131,7 @@ export function signalsFromSnapshot(stars: number, prev: StarSnapshot | undefine
131
131
  return { stars, starsDelta, starsPerDay: starsDelta / span };
132
132
  }
133
133
 
134
- function appendSnapshots(root: string, rows: StarSnapshot[]): void {
134
+ export function appendGithubStarSnapshots(root: string, rows: StarSnapshot[]): void {
135
135
  if (!rows.length) return;
136
136
  mkdirSync(intakeLedgerDir(root), { recursive: true });
137
137
  appendFileSync(githubStarsFile(root), rows.map((r) => JSON.stringify(r)).join('\n') + '\n');
@@ -154,6 +154,8 @@ export interface CollectGithubOpts {
154
154
  /** 스냅숏·비교의 «오늘». 생략하면 UTC 날짜. */
155
155
  day?: string;
156
156
  now?: () => Date;
157
+ /** 등록 원천이 질의 하나만 수집할 때; 생략 시 기존 관심 질의 전부. */
158
+ queries?: readonly string[];
157
159
  }
158
160
 
159
161
  /**
@@ -173,7 +175,7 @@ export async function collectGithubStars(
173
175
  const found: GithubStarRepo[] = [];
174
176
  // «급상승»은 최근에 생긴 저장소 중 별 순이다 — 전체 기간 별 순이면 활발한 거대 저장소가 매일 같은 목록으로 나온다(2026-09-26 검토).
175
177
  const windowStart = new Date(Date.parse(`${day}T00:00:00.000Z`) - days * 86_400_000).toISOString().slice(0, 10);
176
- for (const query of GITHUB_STAR_QUERIES) {
178
+ for (const query of opts.queries ?? GITHUB_STAR_QUERIES) {
177
179
  const rows = await search(`${query} created:>=${windowStart}`, perQuery);
178
180
  const recent = rows.filter((r) => isRecentRepo(r, day, days));
179
181
  queries.push({ query, received: recent.length });
@@ -208,7 +210,7 @@ export async function collectGithubStars(
208
210
  day, days, perQuery, dryRun: !!opts.dryRun, queries, repos, raws: raws.length,
209
211
  };
210
212
  if (!opts.dryRun) {
211
- appendSnapshots(root, repos.map((row) => ({ day, repo: row.repo, stars: row.stars })));
213
+ appendGithubStarSnapshots(root, repos.map((row) => ({ day, repo: row.repo, stars: row.stars })));
212
214
  result.ingest = ingestIntakeItems(root, 'github', raws);
213
215
  }
214
216
  debug.log('intake.collect', 'github-stars', {
@@ -60,14 +60,26 @@ export async function collectTelegramSaved(root: string, fetch: FetchSavedMessag
60
60
  }
61
61
 
62
62
  /** GramJS 사용자 세션으로 `me` 대화를 읽는 실제 구현. 자격이 없으면 이유를 담아 던진다(값은 싣지 않는다). */
63
- export async function gramjsFetchSaved(env: NodeJS.ProcessEnv = process.env): Promise<{ fetch: FetchSavedMessages; close: () => Promise<void> }> {
63
+ type GramjsModules = { TelegramClient: typeof import('telegram').TelegramClient; StringSession: typeof import('telegram/sessions').StringSession };
64
+ const loadGramjs = async (): Promise<GramjsModules> => ({
65
+ TelegramClient: (await import('telegram')).TelegramClient,
66
+ StringSession: (await import('telegram/sessions')).StringSession,
67
+ });
68
+
69
+ export async function gramjsFetchSaved(
70
+ env: NodeJS.ProcessEnv = process.env,
71
+ load: () => Promise<GramjsModules> = loadGramjs,
72
+ ): Promise<{ fetch: FetchSavedMessages; close: () => Promise<void> }> {
64
73
  const apiId = Number(env.TELEGRAM_API_ID ?? 0);
65
74
  const apiHash = env.TELEGRAM_API_HASH ?? '';
66
75
  const session = env.TELEGRAM_USER_SESSION ?? '';
67
76
  const missing = [!apiId && 'TELEGRAM_API_ID', !apiHash && 'TELEGRAM_API_HASH', !session && 'TELEGRAM_USER_SESSION'].filter(Boolean);
68
77
  if (missing.length) throw new Error(`텔레그램 사용자 세션 자격이 없다: ${missing.join(' · ')} (docs/manual/MANUAL-telegram-unmanned-test-2026-07-21.md §1~§3)`);
69
- const { TelegramClient } = await import('telegram');
70
- const { StringSession } = await import('telegram/sessions');
78
+ // `telegram` is an optional dependency: its native websocket helpers have no Linux ARM64 build, so npm may leave it out.
79
+ let modules: GramjsModules;
80
+ try { modules = await load(); }
81
+ catch { throw new Error('텔레그램 사용자 세션 모듈(telegram)이 이 설치에 없다 — 선택 의존이라 Linux ARM64 등에서는 빠진다(npm i -g telegram 으로 따로 깔 수 있다)'); }
82
+ const { TelegramClient, StringSession } = modules;
71
83
  const client = new TelegramClient(new StringSession(session), apiId, apiHash, { connectionRetries: 3 });
72
84
  client.setLogLevel('error' as never); // 연결 로그가 표준출력을 채우지 않게
73
85
  await client.connect();
@@ -0,0 +1,56 @@
1
+ import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
2
+ import {
3
+ submitIntakeWork,
4
+ type SubmitIntakeWorkDeps,
5
+ } from './submit-intake-work.js';
6
+
7
+ export interface DiscordSeatWorkMessage {
8
+ channelId: string;
9
+ messageId: string;
10
+ threadId?: string;
11
+ }
12
+
13
+ export interface DiscordSeatWorkDeps extends SubmitIntakeWorkDeps {
14
+ submit?: typeof submitIntakeWork;
15
+ }
16
+
17
+ /** Addressed Discord work goes through the graph intake door, not the chat turn. */
18
+ export async function handleDiscordSeatWork(
19
+ text: string,
20
+ msg: DiscordSeatWorkMessage,
21
+ deps: DiscordSeatWorkDeps = {},
22
+ ): Promise<string | null> {
23
+ const address = parseSeatAddress(text);
24
+ if (!address) return null;
25
+
26
+ const seats = address.seats.map((name) => ({ name, seat: resolveSeat(name) }));
27
+ const unknown = seats.filter(({ seat }) => !seat).map(({ name }) => `@${name}`);
28
+ if (unknown.length) return `어느 좌석을 말씀하시나요? ${unknown.join(', ')}은(는) 등록된 좌석이 아닙니다. 좌석을 확인해 다시 보내 주세요.`;
29
+ const body = address.body.trim();
30
+ if (!body) return '어떤 일을 맡길까요? 좌석 주소 뒤에 요청 내용을 적어 다시 보내 주세요.';
31
+
32
+ const reportTo = {
33
+ channel: 'discord' as const,
34
+ channelId: msg.channelId,
35
+ ...(msg.threadId ? { discordThreadId: msg.threadId } : {}),
36
+ };
37
+ const replies: string[] = [];
38
+ for (const { seat } of seats) {
39
+ if (!seat) continue;
40
+ const label = seat.title ?? seat.id;
41
+ const result = await (deps.submit ?? submitIntakeWork)({
42
+ text: `@${label} ${body}`,
43
+ track: 'graph',
44
+ origin: {
45
+ kind: 'external', ledgerSource: 'memo', provider: 'other',
46
+ ref: `discord:${msg.channelId}:${msg.messageId}`, reportTo,
47
+ },
48
+ }, deps);
49
+ if (result.ok && result.track === 'graph') {
50
+ replies.push(`@${label} 접수번호: ${result.acceptanceId}`);
51
+ } else {
52
+ replies.push(`@${label} 접수 실패 — ${result.ok ? '접수번호가 없습니다' : result.reason.split('\n')[0]!.slice(0, 200)}`);
53
+ }
54
+ }
55
+ return replies.join('\n');
56
+ }
@@ -0,0 +1,202 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
6
+ import { findTrack } from '../autopilot/mission-phase-track.js';
7
+ import { appendGithubStarSnapshots, collectGithubStars, ghApiSearchRepos, type SearchGithubRepos } from './collect-github.js';
8
+ import { ingestIntakeItems, parseRawIntakeJsonl, type IntakeSource, type RegisteredIntakeSourceKind, type RawIntakeItem } from './items.js';
9
+
10
+ export type SourceKind = 'github-query' | 'rss' | 'command';
11
+ export interface RegisteredIntakeSource {
12
+ id: string;
13
+ seat: string;
14
+ kind: SourceKind;
15
+ spec: string;
16
+ every: string;
17
+ until?: string;
18
+ why?: string;
19
+ lastRunAt?: string;
20
+ }
21
+
22
+ export function canonicalSeat(value: string): string {
23
+ const seat = findTrack(value.trim());
24
+ if (!seat) throw new Error(`알 수 없는 자리: ${value}`);
25
+ return seat.id;
26
+ }
27
+
28
+ export function cadenceMs(every: string): number {
29
+ const match = /^([1-9]\d*)([hdw])$/.exec(every);
30
+ if (!match) throw new Error(`--every 는 Nh · Nd · Nw 형식이어야 한다: ${every}`);
31
+ const ms = Number(match[1]) * ({ h: 3600_000, d: 86_400_000, w: 604_800_000 }[match[2]!] ?? 0);
32
+ if (!Number.isSafeInteger(ms)) throw new Error(`--every 가 너무 크다: ${every}`);
33
+ return ms;
34
+ }
35
+
36
+ function untilMs(until: string): number {
37
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(until) || !Number.isFinite(Date.parse(`${until}T00:00:00Z`))
38
+ || new Date(`${until}T00:00:00Z`).toISOString().slice(0, 10) !== until) throw new Error(`--until 날짜 형식 오류: ${until}`);
39
+ return Date.parse(`${until}T00:00:00Z`);
40
+ }
41
+
42
+ export function expiredSource(source: RegisteredIntakeSource, now: Date): boolean {
43
+ return source.until !== undefined && now.getTime() >= untilMs(source.until);
44
+ }
45
+
46
+ export function sourceIsDue(source: RegisteredIntakeSource, now: Date): boolean {
47
+ return !expiredSource(source, now) && (source.lastRunAt === undefined
48
+ || Date.parse(source.lastRunAt) + cadenceMs(source.every) <= now.getTime());
49
+ }
50
+
51
+ export function sourcesFile(stateDir: string): string { return join(stateDir, 'intake', 'sources.json'); }
52
+
53
+ function readSources(stateDir: string): RegisteredIntakeSource[] {
54
+ const file = sourcesFile(stateDir);
55
+ if (!existsSync(file)) return [];
56
+ const parsed: unknown = JSON.parse(readFileSync(file, 'utf8'));
57
+ if (!Array.isArray(parsed)) throw new Error(`원천 등록부 모양 오류: ${file}`);
58
+ return parsed as RegisteredIntakeSource[];
59
+ }
60
+
61
+ function saveSources(stateDir: string, sources: RegisteredIntakeSource[]): void {
62
+ const file = sourcesFile(stateDir);
63
+ mkdirSync(join(stateDir, 'intake'), { recursive: true });
64
+ const temp = `${file}.${randomUUID()}.tmp`;
65
+ try {
66
+ const fd = openSync(temp, 'wx', 0o600);
67
+ try { writeFileSync(fd, JSON.stringify(sources, null, 2) + '\n'); } finally { closeSync(fd); }
68
+ chmodSync(temp, 0o600);
69
+ renameSync(temp, file);
70
+ } catch (error) {
71
+ if (existsSync(temp)) unlinkSync(temp);
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ function commandArgv(spec: string): string[] {
77
+ // Shell syntax and interpreter wrappers are rejected; the allowed invocation uses execFileSync without a shell.
78
+ if (!spec.trim() || /["'\\`$;|&<>(){}\[\]*?\r\n]/.test(spec)) throw new Error('command spec 은 셸 구문 없이 실행 파일과 인자만 허용한다');
79
+ const argv = spec.trim().split(/\s+/);
80
+ // Allow-list, not deny-list (review R3): any other program (`nodejs scripts/x.ts`, a renamed wrapper) could emit
81
+ // private memos without a privacy mark. 1st edition: only the elanous CLI itself, whose intake commands mark privacy.
82
+ const executable = argv[0]!.split('/').pop()!.toLowerCase();
83
+ if (executable !== 'elanous' && executable !== 'eln') throw new Error('command 원천은 1판에서 elanous CLI(elanous·eln)만 받는다');
84
+ if (argv.some((arg) => /collect-telegram-saved/i.test(arg))) throw new Error('개인 메모(collect-telegram-saved)는 command 원천으로 등록할 수 없다');
85
+ return argv;
86
+ }
87
+
88
+ export function addSource(input: RegisteredIntakeSource, stateDir = effectiveInstanceRoot()): RegisteredIntakeSource {
89
+ const seat = canonicalSeat(input.seat);
90
+ if (!['github-query', 'rss', 'command'].includes(input.kind)) throw new Error(`알 수 없는 원천 종류: ${input.kind}`);
91
+ cadenceMs(input.every);
92
+ if (input.until !== undefined) untilMs(input.until);
93
+ if (!input.id?.trim() || !/^[\w.-]+$/.test(input.id)) throw new Error('원천 id 는 영문·숫자·_·-·. 만 허용한다');
94
+ if (!input.spec?.trim()) throw new Error('원천 spec 이 비었다');
95
+ if (input.kind === 'rss') {
96
+ let protocol = '';
97
+ try { protocol = new URL(input.spec).protocol; } catch { /* validation below */ }
98
+ if (protocol !== 'http:' && protocol !== 'https:') throw new Error('RSS spec 은 HTTP(S) URL 이어야 한다');
99
+ }
100
+ if (input.kind === 'command') commandArgv(input.spec);
101
+ const sources = readSources(stateDir);
102
+ if (sources.some((source) => source.id === input.id)) throw new Error(`이미 등록된 원천 id: ${input.id}`);
103
+ const source = { ...input, seat };
104
+ saveSources(stateDir, [...sources, source]);
105
+ return source;
106
+ }
107
+
108
+ export function listSources(filter: { seat?: string } = {}, stateDir = effectiveInstanceRoot()): RegisteredIntakeSource[] {
109
+ const seat = filter.seat === undefined ? undefined : canonicalSeat(filter.seat);
110
+ return readSources(stateDir).filter((source) => seat === undefined || source.seat === seat);
111
+ }
112
+
113
+ export function removeSource(id: string, stateDir = effectiveInstanceRoot()): boolean {
114
+ const sources = readSources(stateDir);
115
+ if (!sources.some((source) => source.id === id)) return false;
116
+ saveSources(stateDir, sources.filter((source) => source.id !== id));
117
+ return true;
118
+ }
119
+
120
+ export function dueSources(now: Date = new Date(), stateDir = effectiveInstanceRoot()): RegisteredIntakeSource[] {
121
+ return listSources({}, stateDir).filter((source) => sourceIsDue(source, now));
122
+ }
123
+
124
+ export interface RunDueDeps {
125
+ stateDir?: string;
126
+ searchGithub?: SearchGithubRepos;
127
+ fetchFeed?: (url: string) => Promise<string>;
128
+ runCommand?: (command: string) => Promise<string>;
129
+ ingest?: typeof ingestIntakeItems;
130
+ }
131
+
132
+ function xmlText(value: string): string {
133
+ return value.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1').replace(/<[^>]+>/g, '')
134
+ .replace(/&(?:amp|lt|gt|quot|apos|#(\d+)|#x([a-f\d]+));/gi, (entity, dec: string | undefined, hex: string | undefined) => {
135
+ if (dec || hex) return String.fromCodePoint(Number.parseInt(dec ?? hex!, dec ? 10 : 16));
136
+ return ({ '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&apos;': "'" } as Record<string, string>)[entity.toLowerCase()] ?? entity;
137
+ }).trim();
138
+ }
139
+
140
+ export function parseFeed(xml: string): RawIntakeItem[] {
141
+ // A login page or broken XML is a failure, not «0 items» — otherwise lastRunAt moves and the retry waits a whole cycle (review R3).
142
+ const head = xml.replace(/^\uFEFF/, '').replace(/<\?xml[^>]*\?>/i, '').replace(/<!--[\s\S]*?-->/g, '').trimStart();
143
+ if (!/^<(?:rss|feed|rdf:RDF)\b/i.test(head)) throw new Error('RSS/Atom 피드가 아니다(루트가 rss·feed·rdf:RDF 아님)');
144
+ const entries = xml.match(/<(?:item|entry)\b[^>]*>[\s\S]*?<\/(?:item|entry)>/gi) ?? [];
145
+ return entries.map((entry) => {
146
+ const field = (name: string): string => xmlText(entry.match(new RegExp(`<${name}\\b[^>]*>([\\s\\S]*?)<\\/${name}>`, 'i'))?.[1] ?? '');
147
+ const link = entry.match(/<link\b[^>]*\bhref=["']([^"']+)["'][^>]*\/?\s*>/i)?.[1] ?? field('link');
148
+ return { url: xmlText(link), title: field('title'), text: field('description') || field('summary') || field('content'), kind: 'rss' as const };
149
+ }).filter((entry) => !!entry.url);
150
+ }
151
+
152
+ export async function runDue({ now = new Date(), seat, deps = {} }: { now?: Date; seat?: string; deps?: RunDueDeps } = {}): Promise<{ ran: Array<{ id: string; items: number; error?: string }> }> {
153
+ const stateDir = deps.stateDir ?? effectiveInstanceRoot();
154
+ const selectedSeat = seat === undefined ? undefined : canonicalSeat(seat);
155
+ const ran: Array<{ id: string; items: number; error?: string }> = [];
156
+ for (const source of dueSources(now, stateDir).filter((source) => selectedSeat === undefined || source.seat === selectedSeat)) {
157
+ try {
158
+ let raws: RawIntakeItem[];
159
+ let githubSnapshots: { day: string; repo: string; stars: number }[] | undefined;
160
+ let intakeSource: IntakeSource | RegisteredIntakeSourceKind;
161
+ if (source.kind === 'github-query') {
162
+ const collected = await collectGithubStars(stateDir, deps.searchGithub ?? ghApiSearchRepos, {
163
+ queries: [source.spec], day: now.toISOString().slice(0, 10), dryRun: true,
164
+ });
165
+ githubSnapshots = collected.repos.map((repo) => ({ day: collected.day, repo: repo.repo, stars: repo.stars }));
166
+ raws = collected.repos.map((repo) => ({ url: repo.url, title: repo.title, text: repo.text, kind: 'repo',
167
+ signals: { stars: repo.stars, ...(repo.starsDelta === undefined ? {} : { starsDelta: repo.starsDelta }),
168
+ ...(repo.starsPerDay === undefined ? {} : { starsPerDay: repo.starsPerDay }) } }));
169
+ intakeSource = 'github';
170
+ } else if (source.kind === 'rss') {
171
+ const feed = await (deps.fetchFeed ?? (async (url: string) => {
172
+ const response = await fetch(url, { signal: AbortSignal.timeout(30_000) });
173
+ if (!response.ok) throw new Error(`RSS HTTP ${response.status}`);
174
+ return response.text();
175
+ }))(source.spec);
176
+ raws = parseFeed(feed);
177
+ intakeSource = 'rss';
178
+ } else {
179
+ const argv = commandArgv(source.spec);
180
+ const stdout = await (deps.runCommand ?? (async () => execFileSync(argv[0]!, argv.slice(1), { encoding: 'utf8', timeout: 600_000, maxBuffer: 16 * 1024 * 1024 })))(source.spec);
181
+ const parsed = parseRawIntakeJsonl(stdout);
182
+ if (parsed.bad) throw new Error(`잘못된 JSONL ${parsed.bad}줄`);
183
+ if (parsed.raws.some((raw) => (raw as RawIntakeItem & { privacy?: string; source?: string }).privacy === 'user-private'
184
+ || ['memo', 'telegram-saved'].includes((raw as RawIntakeItem & { source?: string }).source ?? ''))) {
185
+ throw new Error('개인 메모(user-private)는 command 원천에서 받을 수 없다');
186
+ }
187
+ raws = parsed.raws;
188
+ intakeSource = 'command';
189
+ }
190
+ const items = (deps.ingest ?? ingestIntakeItems)(stateDir, intakeSource, raws.map((raw) => ({ ...raw, seat: source.seat })), now.toISOString()).shaped;
191
+ if (githubSnapshots) appendGithubStarSnapshots(stateDir, githubSnapshots);
192
+ // Re-read after each run so one failure cannot overwrite another source's successful timestamp.
193
+ const current = readSources(stateDir);
194
+ const found = current.find((entry) => entry.id === source.id);
195
+ if (found) saveSources(stateDir, current.map((entry) => entry.id === source.id ? { ...entry, lastRunAt: now.toISOString() } : entry));
196
+ ran.push({ id: source.id, items });
197
+ } catch (error) {
198
+ ran.push({ id: source.id, items: 0, error: error instanceof Error ? error.message : String(error) });
199
+ }
200
+ }
201
+ return { ran };
202
+ }
@@ -4,19 +4,22 @@ import { createHash } from 'node:crypto';
4
4
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { debug } from '../debug/log.js';
7
+ import { findTrack } from '../autopilot/mission-phase-track.js';
7
8
 
8
9
  export const INTAKE_SOURCES = ['x', 'youtube', 'github', 'telegram-saved', 'telegram-bot', 'memo', 'pwa'] as const;
10
+ export type RegisteredIntakeSourceKind = 'rss' | 'command';
9
11
  export type IntakeSource = typeof INTAKE_SOURCES[number];
10
- export type IntakeKind = 'video' | 'repo' | 'post' | 'article' | 'note';
12
+ export type IntakeKind = 'video' | 'repo' | 'post' | 'article' | 'note' | 'rss';
11
13
  export type IntakePrivacy = 'public' | 'user-private';
12
14
  export const INTAKE_STATUSES = ['new', 'queued', 'absorbed', 'checked', 'routed', 'discarded', 'deferred'] as const;
13
15
  export type IntakeStatus = typeof INTAKE_STATUSES[number];
14
16
 
15
17
  export interface IntakeItem {
16
18
  id: string;
17
- source: IntakeSource;
19
+ source: IntakeSource | RegisteredIntakeSourceKind;
18
20
  /** 같은 항목이 다른 입력원에서도 왔으면 여기 모인다 — «두 입력원에서 동시에 뜬 것»이 신호다. */
19
- sources: IntakeSource[];
21
+ sources: (IntakeSource | RegisteredIntakeSourceKind)[];
22
+ seat?: string;
20
23
  url?: string;
21
24
  kind: IntakeKind;
22
25
  title?: string;
@@ -34,6 +37,7 @@ export interface IntakeItem {
34
37
 
35
38
  /** 수집기가 내는 한 줄 — 모양 맞추기 전. */
36
39
  export interface RawIntakeItem {
40
+ seat?: string;
37
41
  url?: string;
38
42
  title?: string;
39
43
  text?: string;
@@ -76,13 +80,23 @@ export function canonicalUrl(raw: string): string | undefined {
76
80
  return `https://${host}${path}${q}`;
77
81
  }
78
82
 
79
- export function intakeItemId(source: IntakeSource, raw: Pick<RawIntakeItem, 'url' | 'text'>): string {
83
+ function normalizedSeat(seat: unknown): string | undefined {
84
+ if (seat === undefined) return undefined;
85
+ if (typeof seat !== 'string') throw new Error(`알 수 없는 자리: ${String(seat)}`);
86
+ const track = findTrack(seat.trim());
87
+ if (!track) throw new Error(`알 수 없는 자리: ${seat}`);
88
+ return track.id;
89
+ }
90
+
91
+ export function intakeItemId(source: IntakeSource | RegisteredIntakeSourceKind, raw: Pick<RawIntakeItem, 'url' | 'text'> & { seat?: string }): string {
92
+ const seat = normalizedSeat(raw.seat);
80
93
  const url = raw.url ? canonicalUrl(raw.url) : undefined;
81
94
  const basis = url ?? `${source}:${(raw.text ?? '').trim().slice(0, 200)}`;
82
- return createHash('sha256').update(basis).digest('hex').slice(0, 16);
95
+ // Unassigned items retain their original IDs; assigned items have an independent lifecycle per seat.
96
+ return createHash('sha256').update(seat ? JSON.stringify([seat, basis]) : basis).digest('hex').slice(0, 16);
83
97
  }
84
98
 
85
- function kindFor(source: IntakeSource, url: string | undefined): IntakeKind {
99
+ function kindFor(source: IntakeSource | RegisteredIntakeSourceKind, url: string | undefined): IntakeKind {
86
100
  if (url?.startsWith('https://www.youtube.com/')) return 'video';
87
101
  if (url?.startsWith('https://github.com/')) return 'repo';
88
102
  if (url?.startsWith('https://x.com/')) return 'post';
@@ -91,12 +105,13 @@ function kindFor(source: IntakeSource, url: string | undefined): IntakeKind {
91
105
  }
92
106
 
93
107
  /** 개인 메모 입력원은 user-private — 원문을 바깥 LLM 에 보내지 않는다(RFC §3.4). PWA 칸에 사람이 쓴 글도 같다(RFC-pwa-intake-front-door). */
94
- export function privacyFor(source: IntakeSource): IntakePrivacy {
108
+ export function privacyFor(source: IntakeSource | RegisteredIntakeSourceKind): IntakePrivacy {
95
109
  return source === 'telegram-saved' || source === 'memo' || source === 'pwa' ? 'user-private' : 'public';
96
110
  }
97
111
 
98
112
  /** ① 모양 맞추기 — 수집기 한 줄 → IntakeItem. 비어 있으면(URL·본문 둘 다 없음) null. */
99
- export function shapeIntakeItem(source: IntakeSource, raw: RawIntakeItem, now: string): IntakeItem | null {
113
+ export function shapeIntakeItem(source: IntakeSource | RegisteredIntakeSourceKind, raw: RawIntakeItem, now: string): IntakeItem | null {
114
+ const seat = normalizedSeat(raw.seat);
100
115
  const url = raw.url ? canonicalUrl(raw.url) : undefined;
101
116
  const text = raw.text?.trim() || undefined;
102
117
  if (!url && !text) return null;
@@ -104,9 +119,10 @@ export function shapeIntakeItem(source: IntakeSource, raw: RawIntakeItem, now: s
104
119
  const signals: Record<string, number> = {};
105
120
  for (const [k, v] of Object.entries(raw.signals ?? {})) if (Number.isFinite(v)) signals[`${source}.${k}`] = v;
106
121
  return {
107
- id: intakeItemId(source, { url, text }),
122
+ id: intakeItemId(source, { url, text, seat }),
108
123
  source,
109
124
  sources: [source],
125
+ ...(seat ? { seat } : {}),
110
126
  ...(url ? { url } : {}),
111
127
  kind: raw.kind ?? kindFor(source, url),
112
128
  ...(raw.title?.trim() ? { title: raw.title.trim().slice(0, 300) } : {}),
@@ -155,7 +171,7 @@ const SETTLED: ReadonlySet<IntakeStatus> = new Set(['absorbed', 'discarded', 'ro
155
171
  * ② 중복 없애기 — 같은 id 는 새 항목을 만들지 않고 신호·입력원을 합친다.
156
172
  * 이미 흡수·버림·갈래가 끝난 것은 «seen» 으로 세고 상태를 되돌리지 않는다(다시 올리지 않는다).
157
173
  */
158
- export function ingestIntakeItems(instanceRoot: string, source: IntakeSource, raws: RawIntakeItem[], now = new Date().toISOString(),
174
+ export function ingestIntakeItems(instanceRoot: string, source: IntakeSource | RegisteredIntakeSourceKind, raws: RawIntakeItem[], now = new Date().toISOString(),
159
175
  log: (event: string, data: Record<string, unknown>) => void = (e, d) => { debug.log('intake.normalize', e, d); }): IngestResult {
160
176
  const { items: existing, badLines } = loadIntakeLedger(instanceRoot);
161
177
  const result: IngestResult = { shaped: 0, added: 0, merged: 0, seen: 0, skipped: 0, badLines };
@@ -203,9 +219,9 @@ export function markIntakeItem(instanceRoot: string, id: string, patch: { status
203
219
  return true;
204
220
  }
205
221
 
206
- export function listIntakeItems(instanceRoot: string, filter: { status?: IntakeStatus; source?: IntakeSource } = {}): IntakeItem[] {
222
+ export function listIntakeItems(instanceRoot: string, filter: { status?: IntakeStatus; source?: IntakeSource; seat?: string } = {}): IntakeItem[] {
207
223
  return [...loadIntakeLedger(instanceRoot).items.values()]
208
- .filter((i) => (!filter.status || i.status === filter.status) && (!filter.source || i.sources.includes(filter.source)))
224
+ .filter((i) => (!filter.status || i.status === filter.status) && (!filter.source || i.sources.includes(filter.source)) && (!filter.seat || i.seat === filter.seat))
209
225
  .sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
210
226
  }
211
227