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,207 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import type { MissionOrigin } from '../autopilot/mission-origin.js';
6
+ import { debug } from '../debug/log.js';
7
+ import { sendOutbound } from '../domains/outbound-alert.js';
8
+ import { defaultSpillUpload } from '../storage/content-spill.js';
9
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
10
+ import type { ExecRequest } from '../exec-requests/store.js';
11
+ import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
12
+ import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
13
+
14
+ const POLL_MS = 5_000;
15
+ const TIMEOUT_MS = 30 * 60_000;
16
+ const DOC = /전략|한\s*장|원페이저|기획|보도자료|글|메모|초안|\b(?:one-pager|strategy|post|memo|draft)\b/i;
17
+ const CODE = /구현|버그|고쳐|\b(?:fix|implement|PR)\b/i;
18
+ const RESEARCH = /전략|시장|경쟁|\bstrategy\b/i;
19
+ /** EV12a — a question or a request for status / a report is answered, never turned into a code goal. */
20
+ const ASK = /상황|현황|진행|준비|어때|어떻게|알려|보고|정리|요약|공유|\?|?|\b(?:status|update|report|summary|summari[sz]e|how|what)\b/i;
21
+ /** Only an explicit build/fix verb makes a question a code request («버그 현황 알려줘» is still a report). */
22
+ const CODE_ACTION = /구현|고쳐|수정해|만들어\s*줘|\b(?:fix|implement)\b/i;
23
+
24
+ type ExecSnapshot = Pick<ExecRequest, 'status' | 'summary' | 'results'> & { seats?: ExecRequest['seats'] };
25
+ type Source = { title: string; url: string };
26
+
27
+ export interface SeatDocDeps {
28
+ research?: (body: string) => Promise<Source[]>;
29
+ submitExec?: (text: string) => Promise<{ id: string }>;
30
+ getExec?: (id: string) => Promise<ExecSnapshot | null>;
31
+ getFile?: (path: string) => Promise<string>;
32
+ publishFile?: (content: string, key: string) => string | null;
33
+ sendOutbound?: typeof sendOutbound;
34
+ sleep?: (ms: number) => Promise<void>;
35
+ now?: () => number;
36
+ pwaUrl?: () => string | undefined;
37
+ log?: (event: 'submitted' | 'research' | 'settled' | 'delivered' | 'delivery-failed', data: Record<string, unknown>) => void;
38
+ }
39
+
40
+ export function isSeatDocRequest(text: string): boolean {
41
+ const addressed = parseSeatAddress(text);
42
+ if (!addressed || !addressed.seats.every(seat => !!resolveSeat(seat))) return false;
43
+ if (DOC.test(addressed.body) && !CODE.test(addressed.body)) return true;
44
+ return ASK.test(addressed.body) && !CODE_ACTION.test(addressed.body);
45
+ }
46
+
47
+ /** CO1 — anything addressed to the COO seat that is not a code request goes to the planner, which splits it across
48
+ * seats (the A5 exec-request path), instead of becoming a code goal. */
49
+ export function isCooPlannerRequest(text: string): boolean {
50
+ const addressed = parseSeatAddress(text);
51
+ return !!addressed && addressed.seats.some(seat => resolveSeat(seat)?.title === 'COO')
52
+ && addressed.seats.every(seat => !!resolveSeat(seat)) && !!addressed.body.trim() && !CODE.test(addressed.body);
53
+ }
54
+
55
+ async function research(body: string): Promise<Source[]> {
56
+ const cli = fileURLToPath(new URL('../../bin/elanous.mjs', import.meta.url));
57
+ const child = Bun.spawn([process.execPath, cli, '--test', 'research', '--json', '--limit', '5', body], {
58
+ stdout: 'pipe', stderr: 'ignore',
59
+ });
60
+ const [out, code] = await Promise.all([new Response(child.stdout).text(), child.exited]);
61
+ if (code !== 0) throw new Error(`research exit ${code}`);
62
+ const payload = JSON.parse(out) as { output?: unknown; metadata?: { totalHits?: number; perEngine?: Record<string, { error?: string }> } };
63
+ if (typeof payload.output !== 'string') throw new Error('research JSON output missing');
64
+ const sources = [...payload.output.matchAll(/^- \[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/gm)]
65
+ .slice(0, 5).map(([, title, url]) => ({ title: title!, url: url! }));
66
+ if (!sources.length && (!payload.metadata?.totalHits || Object.values(payload.metadata.perEngine ?? {}).some(engine => engine.error))) {
67
+ throw new Error('research unavailable');
68
+ }
69
+ return sources;
70
+ }
71
+
72
+ function daemonUrl(): string {
73
+ const base = resolveDaemonEndpoint({ purpose: 'write' })?.baseUrl;
74
+ if (!base) throw new Error('실행 요청 데몬을 찾을 수 없습니다');
75
+ return base;
76
+ }
77
+
78
+ async function execResponse(path: string, text?: string): Promise<Response> {
79
+ const token = readFileSync(join(getElanousConfigDir(), 'acp-token'), 'utf8').trim();
80
+ const response = await fetch(`${daemonUrl()}/v1/exec-requests${path}`, {
81
+ method: text === undefined ? 'GET' : 'POST',
82
+ headers: { Authorization: `Bearer ${token}`, ...(text === undefined ? {} : { 'Content-Type': 'application/json' }) },
83
+ ...(text === undefined ? {} : { body: JSON.stringify({ text }) }),
84
+ });
85
+ if (!response.ok) throw new Error(`실행 요청 HTTP ${response.status}`);
86
+ return response;
87
+ }
88
+
89
+ async function execApi(path: string, text?: string): Promise<ExecRequest> {
90
+ return await (await execResponse(path, text)).json() as ExecRequest;
91
+ }
92
+
93
+ async function execFile(path: string): Promise<string> {
94
+ return (await execResponse(path.slice('/v1/exec-requests'.length))).text();
95
+ }
96
+
97
+ function chatPwaUrl(): string | undefined {
98
+ const endpoint = resolveDaemonEndpoint({ purpose: 'write' });
99
+ return endpoint?.pwaUrl;
100
+ }
101
+
102
+ function accessiblePwaUrl(value: string | undefined): string | undefined {
103
+ if (!value) return undefined;
104
+ try {
105
+ const url = new URL(value);
106
+ // The owner opens the authenticated PWA, not the bearer-only /v1/exec-requests/files endpoint.
107
+ // A loopback address works on the daemon host, not on the sender's phone.
108
+ if (url.protocol !== 'https:' || !url.hostname || url.hostname === 'localhost'
109
+ || url.hostname === '127.0.0.1' || url.hostname === '0.0.0.0'
110
+ || url.username || url.password || url.pathname !== '/app/' || url.search || url.hash) return undefined;
111
+ return url.toString();
112
+ } catch { return undefined; }
113
+ }
114
+
115
+ async function fileLinks(id: string, result: ExecSnapshot, deps: SeatDocDeps): Promise<Map<string, string>> {
116
+ const links = new Map<string, string>();
117
+ for (const entry of result.results) {
118
+ if (entry.kind !== 'report' && entry.kind !== 'text') continue;
119
+ if (!entry.url.startsWith(`/v1/exec-requests/${encodeURIComponent(id)}/files/`)) continue;
120
+ try {
121
+ // The authenticated daemon route is the only source of bytes; a raw file path is never trusted.
122
+ const content = await (deps.getFile ?? execFile)(entry.url);
123
+ const ext = entry.kind === 'report' ? 'md' : 'txt';
124
+ const digest = createHash('sha256').update(content).digest('hex');
125
+ const key = `seat-doc/${encodeURIComponent(id)}/${digest}.${ext}`;
126
+ const url = (deps.publishFile ?? ((value, path) => defaultSpillUpload(value, path, ext)))(content, key);
127
+ if (url && /^https:\/\/[^/]+/.test(url)) links.set(entry.url, url);
128
+ else links.set(entry.url, `\n${content}`);
129
+ } catch (error) {
130
+ debug.log('intake.seat-doc', 'file-link-failed', { id, reason: String(error).slice(0, 200) });
131
+ }
132
+ }
133
+ return links;
134
+ }
135
+
136
+ function message(id: string, result: ExecSnapshot | null, withoutResearch: boolean, lookupAvailable: boolean, pwaUrl?: string, links = new Map<string, string>()): string {
137
+ const suffix = withoutResearch ? '\n조사 없이 진행했습니다.' : '';
138
+ const location = pwaUrl ? `PWA 에서 확인: ${pwaUrl} (접수번호 ${id})` : `PWA 에서 확인 (접수번호 ${id} · 외부 접속 주소 없음)`;
139
+ if (!result) return `${lookupAvailable ? '아직 도는 중' : '상태를 확인할 수 없어 확인을 종료했습니다'} · ${location}${suffix}`;
140
+ if (result.status === 'failed') {
141
+ const reason = result.summary || result.seats?.find(seat => seat.reason)?.reason || '실행 요청 실패';
142
+ return `문서 요청 실패 — ${reason.split('\n')[0]!.slice(0, 200)} · ${location}${suffix}`;
143
+ }
144
+ const summary = result.summary?.split('\n')[0]?.slice(0, 200) || '문서 요청 완료';
145
+ // File endpoints require an Authorization header even when opened from the same origin.
146
+ // Only send externally usable links for genuinely external artifacts; file names remain findable in the PWA.
147
+ const artifacts = result.results.map(entry => `${entry.title} (${entry.kind})${links.has(entry.url) ? ` · ${links.get(entry.url)}` : entry.kind === 'link' && /^https?:\/\//i.test(entry.url) ? ` · ${entry.url}` : ''}`);
148
+ return `${summary}\n${artifacts.join('\n')}${artifacts.length ? '\n' : ''}${location}${suffix}`;
149
+ }
150
+
151
+ export async function submitSeatDocRequest({ text, reportTo, deps = {} }: {
152
+ text: string; reportTo?: MissionOrigin; deps?: SeatDocDeps;
153
+ }): Promise<{ id: string }> {
154
+ const log = deps.log ?? ((event: Parameters<NonNullable<SeatDocDeps['log']>>[0], data: Record<string, unknown>) => debug.log('intake.seat-doc', event, data));
155
+ const body = parseSeatAddress(text)?.body.trim() ?? text.trim();
156
+ let withoutResearch = false;
157
+ let requestText = text;
158
+ if (RESEARCH.test(body)) {
159
+ try {
160
+ const sources = (await (deps.research ?? research)(body)).slice(0, 5);
161
+ if (sources.length) requestText += `\n\n참고 자료\n${sources.map(source => `${source.title} · ${source.url}`).join('\n')}`;
162
+ log('research', { count: sources.length });
163
+ } catch (error) {
164
+ withoutResearch = true;
165
+ log('research', { failed: true, reason: String(error).slice(0, 200) });
166
+ }
167
+ }
168
+ const { id } = await (deps.submitExec ?? ((value) => execApi('', value)))(requestText);
169
+ if (!id) throw new Error('실행 요청 접수번호가 없습니다');
170
+ log('submitted', { id });
171
+
172
+ const watch = async () => {
173
+ const now = deps.now ?? Date.now;
174
+ const sleep = deps.sleep ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));
175
+ const deadline = now() + TIMEOUT_MS;
176
+ let result: ExecSnapshot | null = null;
177
+ let lookupAvailable = false;
178
+ let lastConfirmedStatus: ExecSnapshot['status'] | undefined;
179
+ while (now() < deadline) {
180
+ try {
181
+ const snapshot = await (deps.getExec ?? ((key) => execApi(`/${encodeURIComponent(key)}`)))(id);
182
+ if (!snapshot) throw new Error('실행 요청을 찾을 수 없습니다');
183
+ lookupAvailable = true;
184
+ lastConfirmedStatus = snapshot.status;
185
+ if (snapshot.status === 'done' || snapshot.status === 'failed') {
186
+ result = snapshot;
187
+ break;
188
+ }
189
+ } catch (error) {
190
+ lookupAvailable = false;
191
+ debug.log('intake.seat-doc', 'poll-error', { id, reason: String(error).slice(0, 200) });
192
+ }
193
+ if (now() < deadline) await sleep(Math.min(POLL_MS, Math.max(0, deadline - now())));
194
+ }
195
+ log('settled', { id, status: result?.status ?? (lookupAvailable ? 'timeout' : 'lookup-unavailable'), lastConfirmedStatus });
196
+ const links = result?.status === 'done' ? await fileLinks(id, result, deps) : new Map<string, string>();
197
+ try {
198
+ const sent = await (deps.sendOutbound ?? sendOutbound)(message(id, result, withoutResearch, lookupAvailable,
199
+ accessiblePwaUrl((deps.pwaUrl ?? chatPwaUrl)()), links), 'report', reportTo);
200
+ log(sent ? 'delivered' : 'delivery-failed', { id });
201
+ } catch (error) {
202
+ log('delivery-failed', { id, reason: String(error).slice(0, 200) });
203
+ }
204
+ };
205
+ void watch();
206
+ return { id };
207
+ }
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * - absorb → 흡수 원장(`ingestIntakeItems`) — URL 이 있으면 URL 마다, 없으면 글 한 덩이(PWA `absorbItemsFromText` 와 같은 규칙).
6
6
  * - tasks → TOX(`dispatchTaskCreate`) — 외부 출처(텔레그램 등)는 `external` 로 실어 M1(backlog ⊕ 승인 대기)을 탄다.
7
- * - graph → 하니스 ask(`handleHarnessAskPost` 를 요청 하나로 부른다 — 흐름을 복제하지 않는다).
7
+ * - graph → 자리 문서 요청은 A5 실행 요청, 나머지는 하니스 ask(`handleHarnessAskPost`).
8
8
  * - ask-human → 실행하지 않는다. 고를 갈래를 돌려준다.
9
9
  *
10
10
  * 로그엔 원문이 아니라 길이만 남긴다(앞문 RFC · `user-private`).
@@ -16,6 +16,7 @@ import type { ExternalTaskSource } from '../task-orchestrator/types.js';
16
16
  import { TASK_DEFAULTS } from '../task-orchestrator/types.js';
17
17
  import { decideIntakeFrontRoute, type IntakeFrontRouteDecision } from './front-route-rules.js';
18
18
  import { ingestIntakeItems, intakeItemId, type IntakeSource, type RawIntakeItem } from './items.js';
19
+ import { isCooPlannerRequest, isSeatDocRequest, submitSeatDocRequest, type SeatDocDeps } from './seat-doc-route.js';
19
20
 
20
21
  export type IntakeWorkTrack = 'absorb' | 'tasks' | 'graph';
21
22
 
@@ -40,6 +41,8 @@ export interface SubmitIntakeWorkDeps {
40
41
  ingest?: typeof ingestIntakeItems;
41
42
  createTask?: (input: CreateTaskInput) => Promise<{ taskId?: string; deduplicated?: boolean; output: string }>;
42
43
  askHarness?: (text: string, origin?: MissionOrigin) => Promise<{ acceptanceId?: string; error?: string }>;
44
+ seatDoc?: typeof submitSeatDocRequest;
45
+ seatDocDeps?: SeatDocDeps;
43
46
  handleHarnessAskPost?: typeof import('../nexus/api/harness-api.js').handleHarnessAskPost;
44
47
  log?: (event: string, data: Record<string, unknown>) => void;
45
48
  }
@@ -130,6 +133,11 @@ export async function submitIntakeWork(
130
133
  return { ok: true, track: 'tasks', taskId: res.taskId, deduplicated: res.deduplicated ?? false };
131
134
  }
132
135
  const reportTo = input.origin.kind === 'external' ? input.origin.reportTo : undefined;
136
+ if (isSeatDocRequest(text) || isCooPlannerRequest(text)) {
137
+ const { id } = await (deps.seatDoc ?? submitSeatDocRequest)({ text, reportTo, deps: deps.seatDocDeps });
138
+ log('submitted', { ...base, acceptanceId: id });
139
+ return { ok: true, track: 'graph', acceptanceId: id };
140
+ }
133
141
  const res = await (deps.askHarness ? deps.askHarness(text, reportTo) : defaultAskHarness(text, reportTo, deps.handleHarnessAskPost));
134
142
  if (!res.acceptanceId) {
135
143
  log('failed', { ...base, reason: res.error ?? 'no-acceptance-id' });
@@ -0,0 +1,72 @@
1
+ import { debug } from '../debug/log.js';
2
+ import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
3
+ import {
4
+ submitIntakeWork,
5
+ type SubmitIntakeWorkDeps,
6
+ } from './submit-intake-work.js';
7
+ import type { TelegramWorkMessage } from './telegram-work.js';
8
+
9
+ export interface TelegramSeatWorkDeps extends SubmitIntakeWorkDeps {
10
+ submit?: typeof submitIntakeWork;
11
+ }
12
+
13
+ /** Addressed Telegram work goes through graph intake, not the chat turn. */
14
+ export async function handleTelegramSeatWork(
15
+ text: string,
16
+ msg: TelegramWorkMessage,
17
+ deps: TelegramSeatWorkDeps = {},
18
+ ): Promise<string | null> {
19
+ if (!text.startsWith('@')) return null;
20
+ const address = parseSeatAddress(text);
21
+ if (!address) return null;
22
+
23
+ const seats = address.seats.map((name) => ({ name, seat: resolveSeat(name) }));
24
+ const names = address.seats;
25
+ debug.log('seat-address.telegram', 'parsed', { seats: names });
26
+ const unknown = seats.filter(({ seat }) => !seat).map(({ name }) => `@${name}`);
27
+ if (unknown.length) {
28
+ debug.log('seat-address.telegram', 'rejected', { seats: names, reason: 'unknown-seat' });
29
+ return `어느 좌석을 말씀하시나요? ${unknown.join(', ')}은(는) 등록된 좌석이 아닙니다. 좌석을 확인해 다시 보내 주세요.`;
30
+ }
31
+ const body = address.body.trim();
32
+ if (!body) {
33
+ debug.log('seat-address.telegram', 'rejected', { seats: names, reason: 'empty-body' });
34
+ return '어떤 일을 맡길까요? 좌석 주소 뒤에 요청 내용을 적어 다시 보내 주세요.';
35
+ }
36
+
37
+ const reportTo = {
38
+ channel: 'telegram' as const,
39
+ chatId: msg.chatId,
40
+ ...(msg.botId ? { botId: msg.botId } : {}),
41
+ ...(msg.threadId ? { threadId: msg.threadId } : {}),
42
+ };
43
+ const replies: string[] = [];
44
+ for (const { seat } of seats) {
45
+ if (!seat) continue;
46
+ const label = seat.title ?? seat.id;
47
+ let result: Awaited<ReturnType<typeof submitIntakeWork>>;
48
+ try {
49
+ result = await (deps.submit ?? submitIntakeWork)({
50
+ text: `@${label} ${body}`,
51
+ track: 'graph',
52
+ origin: {
53
+ kind: 'external', ledgerSource: 'telegram-bot', provider: 'telegram',
54
+ ref: `telegram:${msg.chatId}:${msg.messageId}`, reportTo,
55
+ },
56
+ }, deps);
57
+ } catch (error) {
58
+ // 접수 문이 던져도 사람은 같은 대화에서 결과를 받는다(리뷰 must-fix: 예외 시 응답 누락).
59
+ debug.log('seat-address.telegram', 'rejected', { seats: [label], reason: 'submission-threw' });
60
+ replies.push(`@${label} 접수 실패 — ${String(error instanceof Error ? error.message : error).split('\n')[0]!.slice(0, 200)}`);
61
+ continue;
62
+ }
63
+ if (result.ok && result.track === 'graph') {
64
+ debug.log('seat-address.telegram', 'enqueued', { seats: [label] });
65
+ replies.push(`@${label} 접수번호: ${result.acceptanceId}`);
66
+ } else {
67
+ debug.log('seat-address.telegram', 'rejected', { seats: [label], reason: 'submission-failed' });
68
+ replies.push(`@${label} 접수 실패 — ${result.ok ? '접수번호가 없습니다' : result.reason.split('\n')[0]!.slice(0, 200)}`);
69
+ }
70
+ }
71
+ return replies.join('\n');
72
+ }
@@ -20,7 +20,30 @@ import { getUserConfig, type LLMProviderName, type ReasoningLevel } from '../use
20
20
  * openai-codex 눈금이고 openai 기본값을 덮어쓰지 않는다.
21
21
  * ⛔ Do not import this file from user-config — that is the cycle. */
22
22
  function activeProviderForDefaults(provider?: LLMProviderName): LLMProviderName {
23
- return provider ?? getUserConfig().llm.provider;
23
+ if (provider) return provider;
24
+ const configured = getUserConfig().llm.provider;
25
+ return configured === 'auto' ? decidedAutoProvider() : configured;
26
+ }
27
+
28
+ /** `auto` has no ladder of its own — the tier map answers it with Anthropic's model, so a grok-only
29
+ * (or codex-only) user asked for a Claude model at every tier call site and hit «set ANTHROPIC_API_KEY».
30
+ * Resolve `auto` the way the router does (decideProviderForConfig) and use that provider's ladder.
31
+ * Late require: llm.ts imports this module. */
32
+ let decideAuto: () => LLMProviderName | undefined = () => {
33
+ const { decideProviderForConfig } = require('../llm.js') as typeof import('../llm.js');
34
+ const decided = String(decideProviderForConfig(getUserConfig()).provider).replace(/^auto:/, '');
35
+ return decided && decided !== 'auto' ? decided as LLMProviderName : undefined;
36
+ };
37
+ function decidedAutoProvider(): LLMProviderName {
38
+ try { return decideAuto() ?? 'auto'; } catch { return 'auto'; }
39
+ }
40
+ /** Test seam — swap the auto decision. */
41
+ export function setAutoProviderDecisionForTest(fn: (() => LLMProviderName | undefined) | undefined): void {
42
+ decideAuto = fn ?? (() => {
43
+ const { decideProviderForConfig } = require('../llm.js') as typeof import('../llm.js');
44
+ const decided = String(decideProviderForConfig(getUserConfig()).provider).replace(/^auto:/, '');
45
+ return decided && decided !== 'auto' ? decided as LLMProviderName : undefined;
46
+ });
24
47
  }
25
48
 
26
49
  /** config/tier 해석이 «실패»했을 때만 타는 최후 폴백. 티어 사다리가 안 읽히는
package/src/llm.ts CHANGED
@@ -3942,7 +3942,8 @@ function providerDecisionCandidates(): Array<LLMProvider & { name: ProviderDecis
3942
3942
  return PROVIDER_DECISION_ORDER.map((name) => PROVIDERS[name]! as LLMProvider & { name: ProviderDecisionName });
3943
3943
  }
3944
3944
 
3945
- function codexOAuthAvailable(): boolean {
3945
+ /** Exported (OB1) so onboarding and `setup` judge «is there a usable ChatGPT/Codex login» exactly as the runtime does. */
3946
+ export function codexOAuthAvailable(): boolean {
3946
3947
  try {
3947
3948
  const account = resolveCodexAccount(process.env, { storedHome: (key) => loadTokens(key)?.codexHome });
3948
3949
  if (loadTokens(account.storeKey)) return true;
@@ -4478,9 +4479,9 @@ async function consumeProviderText(
4478
4479
  export async function streamLLM(
4479
4480
  messages: LLMMessage[],
4480
4481
  onChunk: (delta: string, full: string) => void,
4481
- opts: LLMOpts & { provider?: LLMProvider; onResolvedProvider?: (provider: string) => void } = {},
4482
+ opts: LLMOpts & { provider?: LLMProvider; initialProvider?: LLMProvider; onResolvedProvider?: (provider: string, model: string) => void } = {},
4482
4483
  ): Promise<string> {
4483
- let activeProvider = opts.provider ?? resolveDefaultProvider(opts.model);
4484
+ let activeProvider = opts.provider ?? opts.initialProvider ?? resolveDefaultProvider(opts.model);
4484
4485
  const attemptedProviders = new Set<string>([activeProvider.name]);
4485
4486
  let blockedProviders: ProviderFallbackAttempt[] = [];
4486
4487
  let modelOverride = opts.model;
@@ -4493,7 +4494,7 @@ export async function streamLLM(
4493
4494
  );
4494
4495
  activeProvider = finalized.provider;
4495
4496
  const activeModel = finalized.model;
4496
- opts.onResolvedProvider?.(activeProvider.name);
4497
+ opts.onResolvedProvider?.(activeProvider.name, activeModel);
4497
4498
  debug.log('llm.router', 'streamLLM', {
4498
4499
  provider: activeProvider.name,
4499
4500
  model: activeModel,
@@ -0,0 +1,127 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { isIP } from 'node:net';
3
+ import { resolve } from 'node:path';
4
+ import { parseDocument } from 'yaml';
5
+
6
+ export interface MachineRow {
7
+ id: string;
8
+ name: string;
9
+ specs: string;
10
+ locationStatus: string;
11
+ role: string;
12
+ duties: string;
13
+ }
14
+
15
+ export interface DeviceRow {
16
+ id: string;
17
+ name: string;
18
+ status: string;
19
+ purpose: string;
20
+ }
21
+
22
+ export interface MachineLedger {
23
+ title: string;
24
+ policy: string[];
25
+ machines: MachineRow[];
26
+ devices: DeviceRow[];
27
+ rules: string[];
28
+ changes: string[];
29
+ }
30
+
31
+ export const machineLedgerPath = (root = process.cwd()): string => resolve(root, 'docs/ops/machines.yaml');
32
+ export const machineMarkdownPath = (root = process.cwd()): string => resolve(root, 'docs/ops/LEDGER-machines-and-devices.md');
33
+
34
+ // The ledger rule: no secrets, IPs or accounts — token prefixes, key=value assignments, e-mail addresses.
35
+ const forbidden = /(?:sk-(?:proj-|live-|ant-)?|gh[pousr]_|github_pat_|glpat-|xox[abposr]-|tskey-|AKIA|AIza)[A-Za-z0-9_-]{8,}|\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:token|secret|password|passwd|api[_-]?key|key)\s*[=:]\s*\S|-----BEGIN [A-Z ]*PRIVATE KEY|[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/i;
36
+ // A value lands in a Markdown table cell: a pipe or line break would split the row.
37
+ const tableBreaking = /[|\r\n]/;
38
+ const containsIpv6 = (value: string): boolean => (value.match(/(?:[a-f\d]*:){2,}[a-f\d:.]*/gi) ?? []).some((candidate) => isIP(candidate.replace(/\.+$/, '')) === 6);
39
+ const idPattern = /^[a-z][a-z0-9-]*$/;
40
+ const keys = (value: Record<string, unknown>, allowed: string[], label: string): void => {
41
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`unknown ${label} field: ${key}`);
42
+ };
43
+ const record = (value: unknown, label: string): Record<string, unknown> => {
44
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`invalid ${label}`);
45
+ return value as Record<string, unknown>;
46
+ };
47
+ const text = (value: unknown, label: string): string => {
48
+ if (typeof value !== 'string' || !value.trim() || forbidden.test(value) || containsIpv6(value)) throw new Error(`invalid or secret-like ${label}`);
49
+ if (tableBreaking.test(value)) throw new Error(`${label} must not contain a pipe or line break`);
50
+ return value;
51
+ };
52
+ const list = (value: unknown, label: string): string[] => {
53
+ if (!Array.isArray(value)) throw new Error(`invalid ${label}`);
54
+ return value.map((entry, index) => text(entry, `${label}[${index}]`));
55
+ };
56
+
57
+ export function validateMachineLedger(value: unknown): MachineLedger {
58
+ const data = record(value, 'ledger');
59
+ keys(data, ['title', 'policy', 'machines', 'devices', 'rules', 'changes'], 'ledger');
60
+ if (!Array.isArray(data.machines) || !Array.isArray(data.devices)) throw new Error('invalid machines or devices');
61
+ const ids = new Set<string>();
62
+ const rows = <T extends { id: string }>(entries: unknown[], fields: string[], label: string): T[] => entries.map((entry, index) => {
63
+ const row = record(entry, `${label}[${index}]`);
64
+ keys(row, fields, label);
65
+ const result = Object.fromEntries(fields.map((field) => [field, text(row[field], `${label}.${field}`)])) as T;
66
+ if (!idPattern.test(result.id) || ids.has(result.id)) throw new Error(`empty, duplicate or invalid id: ${result.id}`);
67
+ ids.add(result.id);
68
+ return result;
69
+ });
70
+ return {
71
+ title: text(data.title, 'title'),
72
+ policy: list(data.policy, 'policy'),
73
+ machines: rows<MachineRow>(data.machines, ['id', 'name', 'specs', 'locationStatus', 'role', 'duties'], 'machines'),
74
+ devices: rows<DeviceRow>(data.devices, ['id', 'name', 'status', 'purpose'], 'devices'),
75
+ rules: list(data.rules, 'rules'),
76
+ changes: list(data.changes, 'changes'),
77
+ };
78
+ }
79
+
80
+ export function loadMachineLedger(root = process.cwd()): MachineLedger {
81
+ const document = parseDocument(readFileSync(machineLedgerPath(root), 'utf8'), { uniqueKeys: true });
82
+ if (document.errors.length) throw new Error(document.errors.map((error) => error.message).join('; '));
83
+ return validateMachineLedger(document.toJS());
84
+ }
85
+
86
+ export function renderLedgerMarkdown(ledger: MachineLedger): string {
87
+ const data = validateMachineLedger(ledger);
88
+ const names = (name: string) => name === '(미정)' ? name : name.split(' · ').map((part) => `\`${part}\``).join(' · ');
89
+ const portablePolicy = data.policy.find((line) => line.startsWith('창은 '));
90
+ const portableHeading = portablePolicy ? `## 2. 창 (${portablePolicy.slice('창은 '.length).replace(/\.$/, '')})` : '## 2. 창';
91
+ return [
92
+ `# ${data.title}`, '',
93
+ '> 생성 결과 — 보유·역할과 변경 이력의 단일 원본은 [`machines.yaml`](machines.yaml). 이 문서의 행·규칙·변경 이력은 직접 수정하지 않는다. YAML 수정 후 `bun bin/elanous.mjs --test machine render` 로 재생성하고 `bun bin/elanous.mjs --test machine render --check` 로 확인한다.',
94
+ ...data.policy.filter((line) => !line.startsWith('창은 ')).map((line) => `> ${line.replace('`docs/ops/machines.yaml` 에', 'YAML 에')}`),
95
+ '', '## 1. 계산 자원 (일하는 기계)',
96
+ '| 이름(tailnet) | 기종 · 사양 | 위치 · 상태 | 자리 | 맡은 일 |',
97
+ '|---|---|---|---|---|',
98
+ ...data.machines.map((row) => `| ${names(row.name)} | ${row.specs} | ${row.locationStatus} | ${row.role} | ${row.duties} |`),
99
+ '', portableHeading,
100
+ '| 기기 | 상태 | 용도 |', '|---|---|---|',
101
+ ...data.devices.map((row) => `| ${row.name} | ${row.status} | ${row.purpose} |`),
102
+ '', '## 3. 규칙', ...data.rules.map((rule) => `- ${rule}`),
103
+ '', '## 변경', ...data.changes.map((change) => `- ${change}`), '',
104
+ ].join('\n');
105
+ }
106
+
107
+ export function setMachineField(root: string, id: string, field: string, value: string): MachineLedger {
108
+ const ledger = loadMachineLedger(root);
109
+ const row = [...ledger.machines, ...ledger.devices].find((item) => item.id === id);
110
+ if (!row) throw new Error(`unknown machine id: ${id}`);
111
+ if (field === 'id' || !(field in row)) throw new Error(`unknown or immutable field: ${field}`);
112
+ text(value, field);
113
+ const updated = { ...row, [field]: value };
114
+ if ('specs' in row) ledger.machines[ledger.machines.findIndex((item) => item.id === id)] = updated as MachineRow;
115
+ else ledger.devices[ledger.devices.findIndex((item) => item.id === id)] = updated as DeviceRow;
116
+ validateMachineLedger(ledger);
117
+ const yamlPath = machineLedgerPath(root);
118
+ const original = readFileSync(yamlPath, 'utf8');
119
+ const document = parseDocument(original, { uniqueKeys: true });
120
+ const section = 'specs' in row ? 'machines' : 'devices';
121
+ const index = section === 'machines' ? ledger.machines.findIndex((item) => item.id === id) : ledger.devices.findIndex((item) => item.id === id);
122
+ document.setIn([section, index, field], value);
123
+ validateMachineLedger(document.toJS());
124
+ writeFileSync(yamlPath, document.toString(), 'utf8');
125
+ writeFileSync(machineMarkdownPath(root), renderLedgerMarkdown(ledger), 'utf8');
126
+ return ledger;
127
+ }
@@ -0,0 +1,28 @@
1
+ export const INTERNAL_REF_MARKERS = [
2
+ { marker: 'internal-doc-path', pattern: /docs\/(?:marketing|plans|goals|manual|roles|archive|measurements|brand|backlog|harness|system)(?=\/|[\s)\]}>.,;:!?'"`]|$)/g },
3
+ { marker: 'private-pr-number', pattern: /(?:PR #\d{4,}|#\d{5,})/g },
4
+ { marker: 'cut-hash', pattern: /(?:컷|cut) [0-9a-f]{7,40}(?![0-9a-f])/g },
5
+ ] as const;
6
+
7
+ export type InternalRefHit = { file: string; line: number; marker: typeof INTERNAL_REF_MARKERS[number]['marker'] };
8
+
9
+ export function scanPackInternalRefs(files: { path: string; text: string }[]): InternalRefHit[] {
10
+ const hits: InternalRefHit[] = [];
11
+ for (const file of files) {
12
+ for (const [index, line] of file.text.split(/\r?\n/).entries()) {
13
+ for (const { marker, pattern } of INTERNAL_REF_MARKERS) {
14
+ for (const match of line.matchAll(pattern)) {
15
+ if (marker === 'private-pr-number') {
16
+ const start = match.index;
17
+ const end = start + match[0].length;
18
+ if ((start > 0 && /[0-9a-f]/i.test(line[start - 1]!)) ||
19
+ (end < line.length && /[0-9a-f]/i.test(line[end]!)) ||
20
+ /^#[0-9a-f]{6}$/i.test(match[0])) continue;
21
+ }
22
+ hits.push({ file: file.path, line: index + 1, marker });
23
+ }
24
+ }
25
+ }
26
+ }
27
+ return hits;
28
+ }
@@ -5,6 +5,7 @@ import { gunzipSync } from 'node:zlib';
5
5
  import { parse as parseYaml } from 'yaml';
6
6
  import { debug } from '../debug/log.js';
7
7
  import { packDirDeterministic } from './tgz.js';
8
+ import { scanPackInternalRefs, type InternalRefHit } from './pack-internal-refs.js';
8
9
  import { signIndex, verifyIndex, type MarketplaceIndex } from './signed-index.js';
9
10
 
10
11
  type IndexConnector = MarketplaceIndex['plugins'][number]['ai.elanous']['connectors'][number];
@@ -62,11 +63,35 @@ function unpackLocal(archive: Uint8Array, destination: string): void {
62
63
  }
63
64
  }
64
65
 
66
+ function scanArchiveInternalRefs(archive: Uint8Array): InternalRefHit[] {
67
+ const tar = gunzipSync(archive);
68
+ const field = (part: Buffer) => part.toString('utf8').replace(/\0.*$/, '');
69
+ const files: { path: string; text: string }[] = [];
70
+ for (let offset = 0; offset + 512 <= tar.length;) {
71
+ const header = tar.subarray(offset, offset + 512);
72
+ if (header.every(byte => byte === 0)) break;
73
+ const size = parseInt(field(header.subarray(124, 136)).trim(), 8);
74
+ const name = field(header.subarray(0, 100));
75
+ const prefix = field(header.subarray(345, 500));
76
+ const path = prefix ? `${prefix}/${name}` : name;
77
+ const contents = tar.subarray(offset + 512, offset + 512 + size);
78
+ // Recognized text formats remain scannable even if they contain a NUL byte.
79
+ const textFile = /(?:^|\/)(?:README|LICENSE|Dockerfile)(?:\.[^/]*)?$|\.(?:md|mdx|txt|json|jsonc|ya?ml|toml|xml|html?|css|scss|svg|mmd|sh|bash|py|js|jsx|mjs|cjs|ts|tsx|mts|cts|sql|csv)$/i.test(path);
80
+ if (textFile || !contents.includes(0)) {
81
+ // A known text format is decoded leniently — one invalid byte must not hide the rest of the file from the scan.
82
+ try { files.push({ path, text: new TextDecoder('utf-8', { fatal: !textFile }).decode(contents) }); }
83
+ catch { /* Binary files are not scanned as text. */ }
84
+ }
85
+ offset += 512 + Math.ceil(size / 512) * 512;
86
+ }
87
+ return scanPackInternalRefs(files);
88
+ }
89
+
65
90
  export interface PublishResult {
66
91
  ok: boolean;
67
92
  sequence: number;
68
93
  published: Array<{ name: string; version: string; sha256: string; bundled: string[] }>;
69
- skipped: Array<{ dir: string; reason: string }>;
94
+ skipped: Array<{ dir: string; reason: string; hits?: InternalRefHit[] }>;
70
95
  /** Published anyway, but a reviewer should look (e.g. a third-party graph relying on core recipes). */
71
96
  warnings: Array<{ dir: string; graph: string; reason: 'third-party-core-recipe'; recipes: string[] }>;
72
97
  }
@@ -128,8 +153,8 @@ export function publishMarket(input: {
128
153
  currentName = dir;
129
154
  currentVersion = undefined;
130
155
  const manifestPath = join(folder, 'plugin.json');
131
- const skip = (reason: string) => {
132
- skipped.push({ dir, reason });
156
+ const skip = (reason: string, hits?: InternalRefHit[]) => {
157
+ skipped.push({ dir, reason, ...(hits ? { hits } : {}) });
133
158
  debug.log('market.publish', 'skipped', { name: dir, version: currentVersion, sequence, reason });
134
159
  };
135
160
  if (!existsSync(manifestPath)) { skip('missing-plugin.json'); continue; }
@@ -245,6 +270,8 @@ export function publishMarket(input: {
245
270
  if (message.startsWith('bundle-conflict')) { skip('bundle-conflict'); continue; }
246
271
  throw error;
247
272
  }
273
+ const hits = scanArchiveInternalRefs(archive);
274
+ if (hits.length) { skip('internal-reference', hits.slice(0, 20)); continue; }
248
275
  let sha256 = createHash('sha256').update(archive).digest('hex');
249
276
  const recorded = recordedHashes.get(JSON.stringify([manifest.name, manifest.version]));
250
277
  if (recorded && recorded !== sha256) {
@@ -318,7 +345,7 @@ export function publishMarket(input: {
318
345
  signatureText: readFileSync(join(input.outDir, 'index.sig'), 'utf8'), trustedKeys });
319
346
  if (!verified.ok) throw new Error(`index verification failed: ${verified.reason}: ${verified.detail}`);
320
347
  for (const item of published) debug.log('market.publish', 'published', { name: item.name, version: item.version, sequence });
321
- return { ok: true, sequence, published, skipped, warnings };
348
+ return { ok: !skipped.some(item => item.reason === 'internal-reference'), sequence, published, skipped, warnings };
322
349
  } catch (error) {
323
350
  const rawReason = error instanceof Error ? error.message : 'unknown-error';
324
351
  const reason = input.key.privateKeyPem && rawReason.includes(input.key.privateKeyPem) ? 'invalid signing key' : rawReason;