elanous 0.2.5 → 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 (479) hide show
  1. package/.env.example +10 -7
  2. package/README.md +9 -4
  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/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
  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/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  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/{8617.465ca22a319c95b3.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/{8918-482b30ce2a8ebf4c.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/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
  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/{f6f3850d-7f324c6566002113.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-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
  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-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
  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-2bf59c720fe9542c.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/icons/elanous-16.png +0 -0
  123. package/apps/pwa/out/icons/elanous-180.png +0 -0
  124. package/apps/pwa/out/icons/elanous-192.png +0 -0
  125. package/apps/pwa/out/icons/elanous-32.png +0 -0
  126. package/apps/pwa/out/icons/elanous-512.png +0 -0
  127. package/apps/pwa/out/index.html +1 -1
  128. package/apps/pwa/out/index.txt +21 -21
  129. package/apps/pwa/out/intake/index.html +1 -1
  130. package/apps/pwa/out/intake/index.txt +22 -22
  131. package/apps/pwa/out/live/index.html +1 -1
  132. package/apps/pwa/out/live/index.txt +22 -22
  133. package/apps/pwa/out/market/index.html +1 -1
  134. package/apps/pwa/out/market/index.txt +21 -21
  135. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  136. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  137. package/apps/pwa/out/morning/index.html +1 -1
  138. package/apps/pwa/out/morning/index.txt +22 -22
  139. package/apps/pwa/out/observatory/index.html +1 -1
  140. package/apps/pwa/out/observatory/index.txt +22 -22
  141. package/apps/pwa/out/reflection/index.html +1 -1
  142. package/apps/pwa/out/reflection/index.txt +22 -22
  143. package/apps/pwa/out/scheduler/index.html +1 -1
  144. package/apps/pwa/out/scheduler/index.txt +22 -22
  145. package/apps/pwa/out/sessions/index.html +1 -1
  146. package/apps/pwa/out/sessions/index.txt +21 -21
  147. package/apps/pwa/out/settings/devices/index.html +1 -1
  148. package/apps/pwa/out/settings/devices/index.txt +22 -22
  149. package/apps/pwa/out/settings/index.html +1 -1
  150. package/apps/pwa/out/settings/index.txt +22 -22
  151. package/apps/pwa/out/setup/done/index.html +1 -1
  152. package/apps/pwa/out/setup/done/index.txt +22 -22
  153. package/apps/pwa/out/setup/index.html +1 -1
  154. package/apps/pwa/out/setup/index.txt +22 -22
  155. package/apps/pwa/out/share/index.html +1 -1
  156. package/apps/pwa/out/share/index.txt +22 -22
  157. package/apps/pwa/out/showroom/index.html +1 -1
  158. package/apps/pwa/out/showroom/index.txt +21 -21
  159. package/apps/pwa/out/tasks/index.html +1 -1
  160. package/apps/pwa/out/tasks/index.txt +22 -22
  161. package/apps/pwa/out/term/index.html +1 -1
  162. package/apps/pwa/out/term/index.txt +22 -22
  163. package/apps/pwa/out/trace/index.html +1 -1
  164. package/apps/pwa/out/trace/index.txt +22 -22
  165. package/apps/pwa/out/vault/index.html +1 -1
  166. package/apps/pwa/out/vault/index.txt +22 -22
  167. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  168. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  169. package/apps/pwa/out/workflows/index.html +1 -1
  170. package/apps/pwa/out/workflows/index.txt +22 -22
  171. package/apps/pwa/out/workspace/index.html +1 -1
  172. package/apps/pwa/out/workspace/index.txt +22 -22
  173. package/apps/pwa/out/worktrees/index.html +1 -1
  174. package/apps/pwa/out/worktrees/index.txt +22 -22
  175. package/bin/elanous.cjs +107 -0
  176. package/graphs/field/field-feed.yaml +24 -0
  177. package/graphs/field/recipes.yaml +12 -0
  178. package/graphs/landing/landing-heal.yaml +3 -1
  179. package/graphs/landing/recipes.yaml +3 -0
  180. package/graphs/release/recipes.yaml +13 -0
  181. package/graphs/release/release-loop.yaml +13 -4
  182. package/package.json +5 -5
  183. package/plugins/card-followup/README.md +42 -0
  184. package/plugins/card-followup/examples/card-sample.json +7 -0
  185. package/plugins/card-followup/graphs/card-followup.yaml +18 -0
  186. package/plugins/card-followup/graphs/recipes.yaml +16 -0
  187. package/plugins/card-followup/graphs/run-step.ts +276 -0
  188. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  189. package/plugins/card-followup/plugin.json +25 -0
  190. package/plugins/doc-draft/README.md +13 -0
  191. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  192. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  193. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  194. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  195. package/plugins/doc-draft/plugin.json +24 -0
  196. package/plugins/geo-check/README.md +15 -0
  197. package/plugins/geo-check/examples/brand-sample.json +8 -0
  198. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  199. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  200. package/plugins/geo-check/graphs/run-step.ts +198 -0
  201. package/plugins/geo-check/plugin.json +24 -0
  202. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  203. package/plugins/job-coach/README.md +31 -1
  204. package/plugins/job-coach/graphs/run-step.ts +2 -2
  205. package/plugins/job-coach/plugin.json +1 -1
  206. package/plugins/job-coach/plugin.ts +1 -1
  207. package/scripts/affinity/endcard.sh +3 -2
  208. package/scripts/affinity/reframe.sh +3 -1
  209. package/scripts/ci-public-export-leak-gate.ts +48 -0
  210. package/scripts/codex-quota-alert.ts +122 -258
  211. package/scripts/coord-tracks.json +4 -4
  212. package/scripts/daemon-port-baseline.txt +1 -2
  213. package/scripts/field-feed/deliver-node.ts +52 -0
  214. package/scripts/field-feed/feed-node.ts +67 -0
  215. package/scripts/field-feed/lib.ts +162 -0
  216. package/scripts/field-feed/reel-node.ts +24 -0
  217. package/scripts/field-feed/start.ts +62 -0
  218. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  219. package/scripts/install.sh +92 -13
  220. package/scripts/landing-heal/clean-checkout.ts +114 -0
  221. package/scripts/landing-heal/run-mission.ts +38 -3
  222. package/scripts/lib/android-test-device.ts +32 -0
  223. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  224. package/scripts/public-export.ts +8 -4
  225. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  226. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  227. package/scripts/release-loop/env-known-failures.ts +31 -0
  228. package/scripts/release-loop/gate-diff.ts +29 -0
  229. package/scripts/release-loop/gate-node.ts +124 -29
  230. package/scripts/release-loop/known-issues-node.ts +159 -0
  231. package/scripts/release-loop/npm-publish-node.ts +118 -0
  232. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  233. package/scripts/release-loop/release-readiness.ts +58 -0
  234. package/scripts/release-loop/shard-plan.ts +47 -0
  235. package/scripts/release-loop/unattended-release.ts +93 -0
  236. package/scripts/ux/measure-first-text.ts +102 -0
  237. package/scripts/ux/record-page.ts +4 -1
  238. package/scripts/vflow-collect.ts +20 -7
  239. package/scripts/vflow-search.ts +73 -0
  240. package/scripts/video-free-line-test-walker.ts +36 -0
  241. package/scripts/video-pipeline.ts +2 -2
  242. package/skills/explainer-video/SKILL.md +24 -3
  243. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
  244. package/skills/explainer-video/engine/build.mjs +2 -2
  245. package/skills/explainer-video/engine/reel.mjs +235 -0
  246. package/skills/explainer-video/engine/reel.sh +9 -0
  247. package/skills/explainer-video/engine/vo.ts +58 -16
  248. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  249. package/skills/grill-me/SKILL.md +1 -1
  250. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  251. package/src/a2a/server.ts +2 -0
  252. package/src/acp/elanous-aware-peers.ts +30 -0
  253. package/src/acp/server.ts +91 -15
  254. package/src/agent/monad-agent-turn.ts +25 -6
  255. package/src/agent/shared-app-tools.ts +5 -3
  256. package/src/agent-substrate/review-intent.ts +6 -2
  257. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  258. package/src/auth/github-app-token.ts +4 -1
  259. package/src/autopilot/mission-origin.ts +20 -4
  260. package/src/autopilot/mission-phase-track.ts +11 -1
  261. package/src/autopilot/track-agent-shadow.ts +151 -0
  262. package/src/autopilot/track-agent.ts +26 -6
  263. package/src/autopilot/track-courier.ts +2 -2
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/chat/chat-cli.ts +4 -1
  271. package/src/chat/short-question.ts +40 -0
  272. package/src/cli/agent-cli.ts +24 -15
  273. package/src/cli/ask-bare.ts +42 -0
  274. package/src/cli/cli-user-error.ts +25 -0
  275. package/src/cli/codex-credit-plan-view.ts +25 -0
  276. package/src/cli/dependence-gauge.ts +237 -0
  277. package/src/cli/doctor-cli.ts +5 -0
  278. package/src/cli/doctor-modes.ts +48 -0
  279. package/src/cli/intake-cli.ts +12 -2
  280. package/src/cli/intake-source-cli.ts +58 -0
  281. package/src/cli/machine-cli.ts +23 -0
  282. package/src/cli/msg-cli.ts +137 -0
  283. package/src/cli/nexus-ios-bind.ts +25 -8
  284. package/src/cli/phone-link.ts +117 -0
  285. package/src/cli/plugin-cli.ts +52 -0
  286. package/src/cli/pr-cli.ts +36 -0
  287. package/src/cli/private-ledger-commands.ts +31 -0
  288. package/src/cli/provider-cli.ts +5 -0
  289. package/src/cli/release-cli.ts +56 -12
  290. package/src/cli/release-run-if-ready.ts +48 -0
  291. package/src/cli/setup-cli.ts +27 -6
  292. package/src/cli/setup-recommend.ts +8 -0
  293. package/src/cli/usage-cli.ts +11 -1
  294. package/src/codex/setup.ts +27 -8
  295. package/src/dashboard/index.ts +1 -1
  296. package/src/discord.ts +56 -6
  297. package/src/domains/core-tools.ts +37 -14
  298. package/src/domains/outbound-alert.ts +55 -16
  299. package/src/exec-requests/planner.ts +140 -0
  300. package/src/exec-requests/runner.ts +338 -0
  301. package/src/exec-requests/store.ts +76 -0
  302. package/src/field/field-media.ts +250 -0
  303. package/src/field/field-reel.ts +196 -0
  304. package/src/grok/credential.ts +7 -5
  305. package/src/harness/aside-run-status.ts +33 -0
  306. package/src/harness/browser-verify.ts +48 -17
  307. package/src/harness/harness-cli-command.ts +25 -3
  308. package/src/harness/image-load-state.ts +21 -0
  309. package/src/harness/process-garbage.ts +45 -0
  310. package/src/index.ts +104 -38
  311. package/src/instance/leader.ts +13 -1
  312. package/src/intake-plane/collect-github.ts +5 -3
  313. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  314. package/src/intake-plane/discord-seat-work.ts +56 -0
  315. package/src/intake-plane/intake-sources.ts +202 -0
  316. package/src/intake-plane/items.ts +28 -12
  317. package/src/intake-plane/seat-doc-route.ts +207 -0
  318. package/src/intake-plane/submit-intake-work.ts +9 -1
  319. package/src/intake-plane/telegram-seat-work.ts +72 -0
  320. package/src/llm/model-defaults.ts +24 -1
  321. package/src/llm.ts +5 -4
  322. package/src/machines/machine-ledger.ts +127 -0
  323. package/src/market/pack-internal-refs.ts +28 -0
  324. package/src/market/publish.ts +31 -4
  325. package/src/maturity/feature-maturity.ts +65 -0
  326. package/src/model-tier/turn-tier.ts +24 -0
  327. package/src/msg/msg-store.ts +149 -0
  328. package/src/native-tool-catalog.ts +2 -1
  329. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  330. package/src/nexus/api/consult-requests.ts +117 -0
  331. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  332. package/src/nexus/api/exec-requests.ts +51 -0
  333. package/src/nexus/api/field-uploads.ts +180 -0
  334. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  335. package/src/nexus/api/graph-approvals.ts +120 -4
  336. package/src/nexus/api/harness-api.ts +219 -5
  337. package/src/nexus/api/http-server.ts +68 -5
  338. package/src/nexus/api/seat-requests.ts +254 -0
  339. package/src/nexus/api/trace-run-scan.ts +37 -0
  340. package/src/nexus/api/trace.ts +19 -3
  341. package/src/nexus/index.ts +3 -0
  342. package/src/nexus/setup-status.ts +4 -5
  343. package/src/notify/llm-budget-alert.ts +125 -0
  344. package/src/oauth/codex-account-rotation.ts +26 -4
  345. package/src/oauth/codex-account-store.ts +20 -2
  346. package/src/oauth/codex-browser-login.ts +173 -0
  347. package/src/oauth/codex.ts +4 -1
  348. package/src/oauth/fallback-chain.ts +5 -5
  349. package/src/oauth/store.ts +42 -5
  350. package/src/onboarding/continue-here.ts +29 -0
  351. package/src/onboarding/entry-hints.ts +8 -0
  352. package/src/onboarding/full-screen-io.ts +35 -3
  353. package/src/onboarding/help/skills.md +1 -1
  354. package/src/onboarding/non-interactive.ts +1 -0
  355. package/src/onboarding/subscription-hint.ts +27 -0
  356. package/src/onboarding/web-first.ts +83 -0
  357. package/src/onboarding.ts +230 -23
  358. package/src/plugins/maker/node-install.ts +92 -0
  359. package/src/plugins/maker/node-maker.ts +139 -0
  360. package/src/plugins/maker/plugin-maker.ts +220 -0
  361. package/src/policy/llm-policy.ts +268 -0
  362. package/src/prompt-library/session-guidance.ts +7 -1
  363. package/src/prompt-library/universal-preamble.ts +2 -2
  364. package/src/relay/channel.ts +184 -0
  365. package/src/relay/mux.ts +182 -0
  366. package/src/relay/relay-cli.ts +20 -0
  367. package/src/relay/relay-server.ts +220 -0
  368. package/src/relay/test-vectors.json +29 -0
  369. package/src/release-loop/checklist.ts +36 -9
  370. package/src/seat-address/seat-address.ts +34 -0
  371. package/src/self-dev/decompose.ts +1 -0
  372. package/src/self-dev/dev-pipeline.ts +6 -3
  373. package/src/self-dev/run-record-mirror.ts +44 -0
  374. package/src/self-dev/run-store.ts +12 -3
  375. package/src/self-implement/harness-policy-gates.ts +4 -1
  376. package/src/self-implement/report-origin.ts +12 -1
  377. package/src/self-implement/run-ledger-compact.ts +1 -1
  378. package/src/session/chat.ts +17 -5
  379. package/src/session/index.ts +23 -10
  380. package/src/skills/shared-agent-skill-roots.ts +25 -0
  381. package/src/steward/failure-streak.ts +78 -0
  382. package/src/steward/steward-cards.ts +24 -0
  383. package/src/steward/triage-budget.ts +127 -0
  384. package/src/steward/triage-plan.ts +75 -0
  385. package/src/steward/triage.ts +244 -42
  386. package/src/task-cards/card-store.ts +1 -1
  387. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  388. package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
  389. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  390. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  391. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  392. package/src/telegram.ts +145 -2
  393. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  394. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  395. package/src/user-config.ts +84 -4
  396. package/src/version/packed-revision.json +1 -1
  397. package/src/video-pipeline/recipes/character.ts +42 -5
  398. package/src/video-pipeline/recipes/explainer.ts +213 -0
  399. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  400. package/src/video-pipeline/recipes/free-line.ts +16 -3
  401. package/src/video-pipeline/recipes/remote.ts +7 -1
  402. package/src/video-pipeline/vflow-reference.ts +109 -0
  403. package/src/video-pipeline/walk-line.ts +3 -2
  404. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
  411. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
  419. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
  477. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  478. package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
  479. /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -3,7 +3,9 @@ import { join } from 'node:path';
3
3
  import { debug } from '../../debug/log.js';
4
4
  import { decideGraphApproval, lastJsonObject, type GraphRunState } from '../../graph-runner/runner.js';
5
5
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
6
+ import { getElanousConfigDir } from '../../elanous-config-dir.js';
6
7
  import { jsonResponse } from './json-response.js';
8
+ import { applyFeedEdit, feedFolderFor, isFeedPreviewMessage, mediaContentType, readFeedDraft, resolveFeedMedia, writeFeedDraft, type FeedDraft } from './graph-approvals-feed.js';
7
9
 
8
10
  export const GRAPH_APPROVALS_PATH = '/v1/graph-approvals';
9
11
  const SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
@@ -16,15 +18,66 @@ interface GraphApprovalItem {
16
18
  since: string;
17
19
  path: string[];
18
20
  recent: Array<{ nodeId: string; ok: boolean; outcome?: string; summary?: string }>;
21
+ /** EV10d — present only for a `{"kind":"feed-preview"}` approval whose run folder holds a valid draft. */
22
+ feed?: Omit<FeedDraft, 'folder'>;
19
23
  }
20
24
 
25
+ /** One decision this daemon applied through `POST /v1/graph-approvals/:graphId/:runId`. */
26
+ interface GraphApprovalDecided {
27
+ graphId: string;
28
+ runId: string;
29
+ nodeId: string;
30
+ decision: 'approved' | 'rejected';
31
+ decidedAt: string;
32
+ }
33
+
34
+ export const GRAPH_DECISION_RING_CAPACITY = 100;
35
+ export const GRAPH_DECISION_RING_MAX_AGE_MS = 30 * 60 * 1000;
36
+
37
+ /**
38
+ * Recent decisions, in memory. The undecided list drops a run the moment it is decided, so
39
+ * another device (Fold ↔ iPhone) could only see «gone» — this ring lets it say «승인됨/거절됨».
40
+ * ⚠️ In-memory: a daemon restart forgets it, and a decision made outside this process
41
+ * (`elanous graph approve` CLI) is not recorded — clients keep a «decided elsewhere» fallback.
42
+ */
43
+ export function createGraphDecisionRing(opts: { capacity?: number; maxAgeMs?: number; now?: () => number } = {}) {
44
+ const capacity = opts.capacity ?? GRAPH_DECISION_RING_CAPACITY;
45
+ const maxAgeMs = opts.maxAgeMs ?? GRAPH_DECISION_RING_MAX_AGE_MS;
46
+ const now = opts.now ?? Date.now;
47
+ let entries: Array<{ atMs: number; record: GraphApprovalDecided }> = [];
48
+ const prune = () => {
49
+ const cutoff = now() - maxAgeMs;
50
+ entries = entries.filter((entry) => entry.atMs > cutoff).slice(-capacity);
51
+ };
52
+ return {
53
+ record(decision: Omit<GraphApprovalDecided, 'decidedAt'>): void {
54
+ const atMs = now();
55
+ entries.push({ atMs, record: { ...decision, decidedAt: new Date(atMs).toISOString() } });
56
+ prune();
57
+ },
58
+ /** Newest first, pruned by age and capacity. */
59
+ list(): GraphApprovalDecided[] {
60
+ prune();
61
+ return entries.map((entry) => entry.record).reverse();
62
+ },
63
+ };
64
+ }
65
+
66
+ export type GraphDecisionRing = ReturnType<typeof createGraphDecisionRing>;
67
+
68
+ const defaultDecisionRing = createGraphDecisionRing();
69
+
21
70
  interface GraphApprovalsDeps {
71
+ /** Injected in tests; production shares one per-process ring. */
72
+ decisions?: GraphDecisionRing;
22
73
  authorize?: (request: Request) => boolean;
23
74
  /** Credential the request carries, checked independently of same-origin (`bearerCredential`).
24
75
  * A decision needs a bearer token: same-origin alone is not trusted because behind
25
76
  * `tailscale serve` every tailnet peer (Pod included) arrives from loopback. */
26
77
  authReason?: (request: Request) => string | undefined;
27
78
  root?: string;
79
+ /** Config dir whose `field/<slug>` folders may hold feed drafts (EV10d). */
80
+ configDir?: string;
28
81
  }
29
82
 
30
83
  const DECISION_AUTH_REASONS = new Set(['bearer-match', 'temp-token', 'noauth']);
@@ -49,7 +102,16 @@ function undecided(state: GraphRunState, root: string): boolean {
49
102
  && Array.isArray(state.path) && !existsSync(join(root, 'graph-runs', state.graphId, `${state.runId}.json.${state.path.length}.decision.json`));
50
103
  }
51
104
 
52
- function list(root: string): GraphApprovalItem[] {
105
+ function feedFor(state: GraphRunState, configDir: string): Omit<FeedDraft, 'folder'> | undefined {
106
+ if (!isFeedPreviewMessage(state.pending?.message ?? '')) return undefined;
107
+ const folder = feedFolderFor(state, configDir);
108
+ const draft = folder ? readFeedDraft(folder) : null;
109
+ if (!draft) return undefined;
110
+ const { folder: _folder, ...rest } = draft;
111
+ return rest;
112
+ }
113
+
114
+ function list(root: string, configDir: string): GraphApprovalItem[] {
53
115
  const dir = join(root, 'graph-runs');
54
116
  let graphs: Dirent[];
55
117
  try { graphs = readdirSync(dir, { withFileTypes: true }); }
@@ -66,6 +128,7 @@ function list(root: string): GraphApprovalItem[] {
66
128
  const state = readRun(root, graph.name, runId);
67
129
  if (!state || !undecided(state, root) || !Array.isArray(state.nodes)) continue;
68
130
  const pending = state.pending!;
131
+ const feed = feedFor(state, configDir);
69
132
  items.push({
70
133
  graphId: state.graphId, runId: state.runId, nodeId: pending.nodeId, message: pending.message,
71
134
  since: pending.since, path: state.path,
@@ -77,6 +140,7 @@ function list(root: string): GraphApprovalItem[] {
77
140
  ...(typeof output?.summary === 'string' ? { summary: output.summary } : {}),
78
141
  };
79
142
  }),
143
+ ...(feed ? { feed } : {}),
80
144
  });
81
145
  }
82
146
  }
@@ -84,13 +148,17 @@ function list(root: string): GraphApprovalItem[] {
84
148
  }
85
149
 
86
150
  export async function handleGraphApprovals(req: Request, deps: GraphApprovalsDeps = {}): Promise<Response> {
87
- const pathname = new URL(req.url).pathname;
151
+ const url = new URL(req.url);
152
+ const pathname = url.pathname;
153
+ const sub = /^\/v1\/graph-approvals\/([^/]+)\/([^/]+)\/(feed-draft|media)$/.exec(pathname);
88
154
  const match = /^\/v1\/graph-approvals\/([^/]+)\/([^/]+)$/.exec(pathname);
89
- if (pathname !== GRAPH_APPROVALS_PATH && !match) return jsonResponse({ error: 'not-found' }, 404);
155
+ if (pathname !== GRAPH_APPROVALS_PATH && !match && !sub) return jsonResponse({ error: 'not-found' }, 404);
90
156
  if (!deps.authorize?.(req)) return jsonResponse({ error: 'unauthorized' }, 401);
157
+ if (sub) return handleFeed(req, url, sub[1]!, sub[2]!, sub[3] as 'feed-draft' | 'media', deps);
91
158
  if (pathname === GRAPH_APPROVALS_PATH) {
92
159
  if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
93
- return jsonResponse({ items: list(deps.root ?? effectiveInstanceRoot()) });
160
+ // ⭐ `items` is unchanged; `decided` is additive (newest first) for other devices' cards.
161
+ return jsonResponse({ items: list(deps.root ?? effectiveInstanceRoot(), deps.configDir ?? getElanousConfigDir()), decided: (deps.decisions ?? defaultDecisionRing).list() });
94
162
  }
95
163
  if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
96
164
  const authReason = deps.authReason?.(req);
@@ -120,6 +188,54 @@ export async function handleGraphApprovals(req: Request, deps: GraphApprovalsDep
120
188
  if (error instanceof Error && error.message.startsWith('run is not awaiting an undecided approval:')) return jsonResponse({ error: 'already-decided' }, 409);
121
189
  throw error;
122
190
  }
191
+ (deps.decisions ?? defaultDecisionRing).record({ graphId, runId, nodeId, decision });
123
192
  debug.log('approvals.graph', 'decided', { graphId, runId, nodeId, decision, by: `pwa:${authReason}` });
124
193
  return jsonResponse({ graphId, runId, decision });
125
194
  }
195
+
196
+ /** EV10d — `PUT …/:graphId/:runId/feed-draft` (사람 수정 저장) · `GET …/:graphId/:runId/media?path=` (초안 미디어). */
197
+ async function handleFeed(req: Request, url: URL, rawGraphId: string, rawRunId: string, kind: 'feed-draft' | 'media', deps: GraphApprovalsDeps): Promise<Response> {
198
+ let graphId: string;
199
+ let runId: string;
200
+ try { graphId = decodeURIComponent(rawGraphId); runId = decodeURIComponent(rawRunId); }
201
+ catch { return jsonResponse({ error: 'not-found' }, 404); }
202
+ if (!validSegment(graphId) || !validSegment(runId)) return jsonResponse({ error: 'not-found' }, 404);
203
+ const root = deps.root ?? effectiveInstanceRoot();
204
+ const state = readRun(root, graphId, runId);
205
+ const folder = state && isFeedPreviewMessage(state.pending?.message ?? '') ? feedFolderFor(state, deps.configDir ?? getElanousConfigDir()) : null;
206
+ if (!state || !folder) {
207
+ debug.log('approvals.feed', 'refused', { graphId, reason: 'not-feed' });
208
+ return jsonResponse({ error: 'not-found' }, 404);
209
+ }
210
+ if (kind === 'media') {
211
+ if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
212
+ const file = resolveFeedMedia(folder, url.searchParams.get('path') ?? '');
213
+ if (!file) {
214
+ debug.log('approvals.feed', 'refused', { graphId, reason: 'media-path' });
215
+ return jsonResponse({ error: 'not-found' }, 404);
216
+ }
217
+ debug.log('approvals.feed', 'media-served', { graphId });
218
+ return new Response(Bun.file(file), { headers: { 'content-type': mediaContentType(file), 'cache-control': 'private, max-age=60' } });
219
+ }
220
+ if (req.method !== 'PUT') return jsonResponse({ error: 'method-not-allowed' }, 405);
221
+ const authReason = deps.authReason?.(req);
222
+ if (!authReason || !DECISION_AUTH_REASONS.has(authReason)) {
223
+ debug.log('approvals.feed', 'refused', { graphId, reason: 'pairing-required' });
224
+ return jsonResponse({ error: 'pairing-required' }, 403);
225
+ }
226
+ if (!undecided(state, root)) return jsonResponse({ error: 'already-decided' }, 409);
227
+ const current = readFeedDraft(folder);
228
+ if (!current) return jsonResponse({ error: 'not-found' }, 404);
229
+ let body: unknown;
230
+ try { body = await req.json(); }
231
+ catch { return jsonResponse({ error: 'bad_request' }, 400); }
232
+ const result = applyFeedEdit(current, body);
233
+ if (!result.ok) {
234
+ debug.log('approvals.feed', 'refused', { graphId, reason: result.error });
235
+ return jsonResponse({ error: result.error }, 400);
236
+ }
237
+ writeFeedDraft(folder, result.draft);
238
+ debug.log('approvals.feed', 'draft-saved', { graphId, revision: result.draft.revision });
239
+ const { folder: _folder, ...feed } = result.draft;
240
+ return jsonResponse({ graphId, runId, feed });
241
+ }
@@ -1,5 +1,7 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
2
- import { relative } from 'node:path';
1
+ import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { hasTerminalRunStatus, loadRunLedger, runLedgerDir, runLedgerPath, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
4
+ import { summarizeTerminalLedger } from '../../self-implement/run-ledger-compact.js';
3
5
  import { debug } from '../../debug/log.js';
4
6
  import { enqueueSoftStop } from '../../harness/control-inbox.js';
5
7
  import { listHarnessScreens, readHarnessScreenTail } from '../../harness/harness-screen.js';
@@ -103,6 +105,10 @@ export interface HarnessApiDeps {
103
105
  readonly createAcceptanceId?: () => string;
104
106
  readonly log?: (event: string, data: Record<string, unknown>) => void;
105
107
  readonly createFeedbackEmitter?: (acceptanceId: string) => (env: FeedbackEnvelope) => void | Promise<void>;
108
+ /** 시험 심 — `?finishedSince=` 가 읽는 원장 디렉터리(기본 `runLedgerDir()`). */
109
+ readonly ledgerDir?: string;
110
+ /** 시험 심 — `?finishedSince=` 의 `landed`(gh 실행기·저장소·시간 초과). */
111
+ readonly landed?: LandedPrsDeps;
106
112
  }
107
113
 
108
114
  function json(body: unknown, status = 200): Response {
@@ -338,10 +344,218 @@ export function handleHarnessAskStatusGet(req: Request, _metaApi: HarnessMetaApi
338
344
  });
339
345
  }
340
346
 
341
- /** Return the structured shared running-runs observation without human rendering. */
342
- export function handleHarnessRunsGet(_req: Request, _metaApi: HarnessMetaApi, deps: HarnessApiDeps = {}): Response {
347
+ /** One terminated run for `GET /v1/harness/runs?finishedSince=` (iPhone «오늘 끝난 일» · 2026-09-30). */
348
+ export interface HarnessFinishedRun {
349
+ runId: string;
350
+ status: string;
351
+ endedAt: string;
352
+ /** 마지막 `run-status` 의 `stage`(예: `merged`·`pr-declined`) — 칩을 더 정확히 고를 때. */
353
+ stage?: string;
354
+ objective?: string;
355
+ prUrl?: string;
356
+ /** `merged` 이벤트의 값 — 이벤트가 없으면 칸이 없다(«병합 안 됨»으로 접지 않는다). */
357
+ merged?: boolean;
358
+ /** 마지막 `merge-decision` 의 `decision`(`auto`·`hitl`) — 병합 «결정»이지 병합 «사실»이 아니다. */
359
+ mergeDecision?: string;
360
+ }
361
+
362
+ export const FINISHED_RUNS_LIMIT = 20;
363
+ export const FINISHED_RUNS_MAX_FILES = 300;
364
+ export const FINISHED_RUNS_MAX_FILE_BYTES = 5 * 1024 * 1024;
365
+ const FINISHED_OBJECTIVE_MAX_CHARS = 160;
366
+
367
+ /**
368
+ * 원장 디렉터리에서 `sinceMs` 이후에 쓰인 «끝난» 런을 요약한다. 읽기는 묶여 있다 —
369
+ * mtime 최신 {@link FINISHED_RUNS_MAX_FILES} 개만 보고, {@link FINISHED_RUNS_MAX_FILE_BYTES} 를 넘는 파일은 건너뛴다.
370
+ */
371
+ export function collectFinishedRuns(dir: string, sinceMs: number): { runs: HarnessFinishedRun[]; scannedFiles: number; skippedFiles: number } {
372
+ let names: string[];
373
+ try { names = readdirSync(dir); }
374
+ catch { return { runs: [], scannedFiles: 0, skippedFiles: 0 }; }
375
+ const candidates: Array<{ runId: string; mtimeMs: number; size: number }> = [];
376
+ for (const name of names) {
377
+ if (!name.endsWith('.jsonl')) continue;
378
+ try {
379
+ const st = statSync(join(dir, name));
380
+ if (st.isFile() && st.mtimeMs >= sinceMs) candidates.push({ runId: name.slice(0, -'.jsonl'.length), mtimeMs: st.mtimeMs, size: st.size });
381
+ } catch { /* 사이에 지워졌다 */ }
382
+ }
383
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
384
+ const scanned = candidates.slice(0, FINISHED_RUNS_MAX_FILES);
385
+ let skippedFiles = candidates.length - scanned.length;
386
+ const runs: HarnessFinishedRun[] = [];
387
+ for (const file of scanned) {
388
+ if (file.size > FINISHED_RUNS_MAX_FILE_BYTES) { skippedFiles += 1; continue; }
389
+ let entries: RunLedgerEntry[] | null;
390
+ try { entries = loadRunLedger(file.runId, dir); }
391
+ catch { skippedFiles += 1; continue; }
392
+ if (!entries?.length || !hasTerminalRunStatus(entries)) continue;
393
+ let summary: ReturnType<typeof summarizeTerminalLedger>;
394
+ try { summary = summarizeTerminalLedger(file.runId, entries); }
395
+ catch { skippedFiles += 1; continue; }
396
+ if (!(Date.parse(summary.endedAt) >= sinceMs)) continue;
397
+ const feature = entries.find((entry) => entry.event === 'start' && typeof entry.data.feature === 'string')?.data.feature as string | undefined;
398
+ const objective = feature?.trim().slice(0, FINISHED_OBJECTIVE_MAX_CHARS);
399
+ const mergedEvent = [...entries].reverse().find((entry) => entry.event === 'merged' && typeof entry.data.merged === 'boolean');
400
+ const decision = [...entries].reverse().find((entry) => entry.event === 'merge-decision' && typeof entry.data.decision === 'string');
401
+ const statusEntry = [...entries].reverse().find((entry) => entry.event === 'run-status' && typeof entry.data.stage === 'string');
402
+ runs.push({
403
+ runId: summary.runId,
404
+ status: summary.status,
405
+ endedAt: summary.endedAt,
406
+ ...(statusEntry ? { stage: statusEntry.data.stage as string } : {}),
407
+ ...(objective ? { objective } : {}),
408
+ ...(summary.prUrl ? { prUrl: summary.prUrl } : {}),
409
+ ...(mergedEvent ? { merged: mergedEvent.data.merged as boolean } : {}),
410
+ ...(decision ? { mergeDecision: decision.data.decision as string } : {}),
411
+ });
412
+ }
413
+ runs.sort((a, b) => Date.parse(b.endedAt) - Date.parse(a.endedAt));
414
+ return { runs: runs.slice(0, FINISHED_RUNS_LIMIT), scannedFiles: scanned.length, skippedFiles };
415
+ }
416
+
417
+ /** 도는 런의 목표 — 그 런 원장의 첫 `start.data.feature`(≤160자). 원장 경로는 그 항목이 준 디렉터리들만 본다(런 수만큼만 읽는다). */
418
+ export function runObjectiveFromLedgers(runId: string, directories: readonly string[]): string | undefined {
419
+ for (const dir of directories) {
420
+ try {
421
+ if (statSync(runLedgerPath(runId, dir)).size > FINISHED_RUNS_MAX_FILE_BYTES) continue;
422
+ const feature = loadRunLedger(runId, dir)?.find((entry) => entry.event === 'start' && typeof entry.data.feature === 'string')?.data.feature;
423
+ const objective = typeof feature === 'string' ? feature.trim().slice(0, FINISHED_OBJECTIVE_MAX_CHARS) : '';
424
+ if (objective) return objective;
425
+ } catch { /* 없거나 못 읽음 — 다음 디렉터리 */ }
426
+ }
427
+ return undefined;
428
+ }
429
+
430
+ // ── 오늘 착지(= main 에 병합된 PR) — 원장은 Pod·시험 우주의 런을 못 보므로 GitHub 이 정본이다 ──
431
+
432
+ /** `landed` 한 줄 — `gh pr list --json number,title,url,mergedAt` 모양 그대로. */
433
+ export interface HarnessLandedPr {
434
+ number: number;
435
+ title: string;
436
+ url: string;
437
+ mergedAt: string;
438
+ }
439
+
440
+ export const LANDED_PRS_LIMIT = 30;
441
+ export const LANDED_PRS_TIMEOUT_MS = 4_000;
442
+ const LANDED_CACHE_MS = 60_000;
443
+ const landedCache = new Map<string, { at: number; prs: HarnessLandedPr[]; truncated: boolean }>();
444
+ let cachedRepoSlug: string | null | undefined;
445
+
446
+ export function _resetLandedPrsCacheForTest(): void {
447
+ landedCache.clear();
448
+ cachedRepoSlug = undefined;
449
+ }
450
+
451
+ export interface LandedPrsDeps {
452
+ /** gh 실행기(기본 = `git-fs/gh-cli` 의 재시도 게이트웨이 · 비동기). */
453
+ readonly runGh?: (args: string[]) => Promise<{ ok: boolean; exitCode: number; stdout: Buffer | string; stderr: Buffer | string }>;
454
+ /** 저장소 `owner/name` — 기본은 데몬이 아는 저장소 뿌리(`harness.defaultRepo` → cwd)의 origin. */
455
+ readonly repoSlug?: () => Promise<string | null>;
456
+ readonly timeoutMs?: number;
457
+ readonly now?: () => number;
458
+ }
459
+
460
+ async function defaultRepoSlug(): Promise<string | null> {
461
+ if (cachedRepoSlug !== undefined) return cachedRepoSlug;
462
+ const [{ resolveWorktreesRepoRoot }, { slugFromRemote }] = await Promise.all([import('./worktrees.js'), import('./live-shipped.js')]);
463
+ const root = resolveWorktreesRepoRoot();
464
+ if (!root) return (cachedRepoSlug = null);
465
+ const proc = Bun.spawn(['git', '-C', root, 'remote', 'get-url', 'origin'], { stdout: 'pipe', stderr: 'ignore' });
466
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
467
+ return (cachedRepoSlug = code === 0 ? slugFromRemote(out) : null);
468
+ }
469
+
470
+ async function defaultRunGh(args: string[]) {
471
+ const { runGhCliWithResultAsync } = await import('../../git-fs/gh-cli.js');
472
+ return runGhCliWithResultAsync(args);
473
+ }
474
+
475
+ function isLandedPr(value: unknown): value is HarnessLandedPr {
476
+ const v = value as Partial<HarnessLandedPr> | null;
477
+ return !!v && typeof v.number === 'number' && typeof v.title === 'string' && typeof v.url === 'string' && typeof v.mergedAt === 'string';
478
+ }
479
+
480
+ /** `sinceMs` 이후 main 에 병합된 PR(최신순 ≤30). 실패·시간 초과는 던지지 않고 `error` 로 돌려준다. 성공만 60초 캐시한다. */
481
+ export async function listLandedPrs(sinceMs: number, deps: LandedPrsDeps = {}): Promise<{ prs: HarnessLandedPr[]; error?: string; cached?: boolean; truncated?: boolean }> {
482
+ const now = deps.now?.() ?? Date.now();
483
+ const sinceIso = new Date(Math.floor(sinceMs / 60_000) * 60_000).toISOString().replace(/\.\d{3}Z$/, 'Z');
484
+ const hit = landedCache.get(sinceIso);
485
+ if (hit && now - hit.at < LANDED_CACHE_MS) return { prs: hit.prs, cached: true, ...(hit.truncated ? { truncated: true } : {}) };
486
+ const timeoutMs = deps.timeoutMs ?? LANDED_PRS_TIMEOUT_MS;
487
+ let timer: ReturnType<typeof setTimeout> | undefined;
488
+ const timeout = new Promise<{ prs: HarnessLandedPr[]; error: string }>((resolve) => {
489
+ timer = setTimeout(() => resolve({ prs: [], error: `timeout ${timeoutMs}ms` }), timeoutMs);
490
+ });
491
+ const work = (async (): Promise<{ prs: HarnessLandedPr[]; error?: string; truncated?: boolean }> => {
492
+ try {
493
+ const slug = await (deps.repoSlug ?? defaultRepoSlug)();
494
+ if (!slug) return { prs: [], error: 'no-repository' };
495
+ const result = await (deps.runGh ?? defaultRunGh)([
496
+ 'pr', 'list', '--repo', slug, '--state', 'merged', '--base', 'main',
497
+ '--search', `merged:>=${sinceIso}`,
498
+ '--json', 'number,title,url,mergedAt', '--limit', String(LANDED_PRS_LIMIT),
499
+ ]);
500
+ if (!result.ok) {
501
+ const stderr = String(result.stderr).trim().split('\n')[0] ?? '';
502
+ return { prs: [], error: `gh failed rc=${result.exitCode}${stderr ? `: ${redactSecretText(stderr).slice(0, 120)}` : ''}` };
503
+ }
504
+ const parsed: unknown = JSON.parse(String(result.stdout));
505
+ if (!Array.isArray(parsed)) return { prs: [], error: 'gh returned non-array' };
506
+ const prs = parsed.filter(isLandedPr)
507
+ .filter((pr) => Date.parse(pr.mergedAt) >= sinceMs)
508
+ .sort((a, b) => Date.parse(b.mergedAt) - Date.parse(a.mergedAt));
509
+ // gh 가 상한만큼 줬으면 더 있을 수 있다 — «전부»라고 말하지 않는다.
510
+ const truncated = parsed.length >= LANDED_PRS_LIMIT;
511
+ landedCache.set(sinceIso, { at: now, prs, truncated });
512
+ return { prs, ...(truncated ? { truncated: true } : {}) };
513
+ } catch (error) {
514
+ return { prs: [], error: message(error).slice(0, 120) };
515
+ }
516
+ })();
517
+ try {
518
+ return await Promise.race([work, timeout]);
519
+ } finally {
520
+ if (timer) clearTimeout(timer);
521
+ }
522
+ }
523
+
524
+ /** Return the structured shared running-runs observation without human rendering.
525
+ * `?finishedSince=<epoch ms>` 가 있으면(iPhone «오늘» · 2026-09-30) 셋을 더한다 —
526
+ * `finished`(이 우주 원장의 끝난 런 · 최신순 ≤20) · `landed`(그 시각 뒤 main 에 병합된 PR · ≤30 · 실패면 `[]` ⊕ `landedError`) ·
527
+ * 도는 항목마다 `objective`(그 런 원장의 `start.feature`). 없으면 응답은 종전 그대로다(동기 응답). */
528
+ export function handleHarnessRunsGet(req: Request, _metaApi: HarnessMetaApi, deps: HarnessApiDeps = {}): Response | Promise<Response> {
343
529
  const runs: RunningRunsResult = (deps.queryRunningRuns ?? queryRunningRuns)({ includeTest: false });
344
- return json(runs);
530
+ const raw = new URL(req.url).searchParams.get('finishedSince');
531
+ if (raw === null) return json(runs);
532
+ const sinceMs = Number(raw);
533
+ if (!raw.trim() || !Number.isFinite(sinceMs) || sinceMs < 0) {
534
+ return json({ error: 'usage: GET /v1/harness/runs?finishedSince=<epoch ms>' }, 400);
535
+ }
536
+ return (async () => {
537
+ const landedWork = listLandedPrs(sinceMs, deps.landed ?? {});
538
+ const dir = deps.ledgerDir ?? runLedgerDir();
539
+ const finished = collectFinishedRuns(dir, sinceMs);
540
+ const entries = runs.entries.map((entry) => {
541
+ const objective = runObjectiveFromLedgers(entry.runId, entry.ledgerDirectories?.length ? entry.ledgerDirectories : [dir]);
542
+ return objective ? { ...entry, objective } : entry;
543
+ });
544
+ const landed = await landedWork;
545
+ debug.log('harness-http', 'runs-finished', {
546
+ sinceMs, count: finished.runs.length, scannedFiles: finished.scannedFiles, skippedFiles: finished.skippedFiles,
547
+ landed: landed.prs.length, landedCached: landed.cached === true, ...(landed.error ? { landedError: landed.error } : {}),
548
+ });
549
+ return json({
550
+ ...runs,
551
+ entries,
552
+ finished: finished.runs,
553
+ finishedObservation: { sinceMs, scannedFiles: finished.scannedFiles, skippedFiles: finished.skippedFiles, limit: FINISHED_RUNS_LIMIT },
554
+ landed: landed.prs,
555
+ ...(landed.truncated ? { landedTruncated: true } : {}),
556
+ ...(landed.error ? { landedError: landed.error } : {}),
557
+ });
558
+ })();
345
559
  }
346
560
 
347
561
  /** Return the human-readable skeleton events persisted for one harness run. */
@@ -15,6 +15,7 @@
15
15
  // Port policy (N1-2 decision): auto-pick from 31415 upward. Actual port
16
16
  // is recorded in runtime.json + printed in boot banner.
17
17
 
18
+ import { getUserConfig } from '../../user-config.js';
18
19
  import type { TelegramEvent } from '../../workflow-runtime/triggers/telegram-source.js';
19
20
  import { spawnSync } from 'node:child_process';
20
21
  import { compareTokenConstTime } from '../../acp/transport/auth.js';
@@ -198,6 +199,11 @@ import { handleMcpResourceGet, MCP_RESOURCE_ROUTE_PATH } from './mcp-resource-ro
198
199
  import { APPROVALS_MERGES_PATH, DESIGN_DIRECTION_PATH, DESIGN_PREVIEW_PATH_PREFIX, DESIGN_PREVIEWS_PATH, DESIGN_SYSTEM_PATH, IPA_PATH_PREFIX, MANIFEST_PATH } from './rest-route-paths.js';
199
200
  import { handleMergeApprovals } from './merge-approvals.js';
200
201
  import { GRAPH_APPROVALS_PATH, handleGraphApprovals } from './graph-approvals.js';
202
+ import { handleExecRequests } from './exec-requests.js';
203
+ import { handleSeatRequests, SEAT_REQUESTS_PATH, type SeatRequestsDeps } from './seat-requests.js';
204
+ import { handleConsultRequests, CONSULT_REQUESTS_PATH, type ConsultRequestsDeps } from './consult-requests.js';
205
+ import { ExecRequestRunner } from '../../exec-requests/runner.js';
206
+ import { FIELD_SERVER_MAX_BODY_BYTES, FIELD_UPLOADS_PATH, FIELD_REEL_PATH, FIELD_REEL_FILE_PATH, handleFieldUploads } from './field-uploads.js';
201
207
  import { handleLiveDetail, LIVE_DETAIL_PATH } from './live-detail.js';
202
208
  import { handleLiveShipped, LIVE_SHIPPED_PATH } from './live-shipped.js';
203
209
  import { handleMcpWidgetCall, MCP_WIDGET_CALL_ROUTE_PATH, persistWidgetTurnToSessionStore } from './mcp-widget-call-route.js';
@@ -235,6 +241,7 @@ import {
235
241
  parseErrorsPath,
236
242
  } from './errors.js';
237
243
  import { handleChatBackendDetection } from './chat-backend-detection.js';
244
+ import { handleChatFastPathConfig } from './chat-fast-path-config.js';
238
245
  import { connectInfoTokenAllowed, handleConnectInfoGet, handleConnectTokenMint, type ConnectInfoCtx } from './connect-info.js';
239
246
  import { handleStaticAppRequest, pathMatchesStaticPrefix } from './static-app.js';
240
247
  import {
@@ -445,6 +452,9 @@ export type NexusWsBridgeInit = Omit<WsBridgeOpts, 'hostname' | 'port'>;
445
452
  export interface NexusHttpServerOpts {
446
453
  /** Optional installer state root for isolated API consumers and tests. */
447
454
  pluginStateRoot?: string;
455
+ execRequests?: ExecRequestRunner;
456
+ seatRequests?: SeatRequestsDeps;
457
+ consultRequests?: ConsultRequestsDeps;
448
458
  state: NexusState;
449
459
  registry: TabRegistry;
450
460
  eventBus: NexusEventBus;
@@ -739,12 +749,16 @@ export function startNexusHttpServer(opts: NexusHttpServerOpts): NexusHttpServer
739
749
  port,
740
750
  hostname,
741
751
  idleTimeout: 255,
752
+ // Bun 기본 128 MB 로는 현장 업로드(파일 200 MB · 요청 500 MB · JSON base64)가 못 들어온다.
753
+ maxRequestBodySize: FIELD_SERVER_MAX_BODY_BYTES,
742
754
  fetch: async (req: Request, srv: unknown) =>
743
755
  (await routeRequest(req, opts, srv as BunServerLike, bridge, devProxyRef, { hostname, port })) ??
744
756
  new Response(null, { status: 101 }),
745
757
  ...(websocketHandler ? { websocket: websocketHandler } : {}),
746
758
  } as unknown as Parameters<typeof Bun.serve>[0];
747
759
  server = Bun.serve(fetchOpts);
760
+ // 데몬이 «실제로» 떴을 때만 재시작 복구(맡긴 일) — 주입된 러너(시험)는 건드리지 않는다.
761
+ if (!opts.execRequests) { try { execRequestRunner().reconcileInterrupted(); } catch { /* 복구 실패가 서버 기동을 막지 않는다 */ } }
748
762
  resolvedPort = port;
749
763
  break;
750
764
  } catch (err) {
@@ -783,6 +797,13 @@ function isPortBusy(err: unknown): boolean {
783
797
  return /EADDRINUSE|address already in use/i.test(msg);
784
798
  }
785
799
 
800
+ let execRequestRunnerInstance: ExecRequestRunner | undefined;
801
+ /** 게으른 한 벌 — import 만으로는 만들지 않는다(재시작 복구가 import 에 묶이지 않게). */
802
+ function execRequestRunner(): ExecRequestRunner {
803
+ execRequestRunnerInstance ??= new ExecRequestRunner();
804
+ return execRequestRunnerInstance;
805
+ }
806
+
786
807
  export async function routeRequest(
787
808
  req: Request,
788
809
  opts: NexusHttpServerOpts,
@@ -831,16 +852,26 @@ export async function routeRequest(
831
852
  // Per-route checkAuth calls below stay in place.
832
853
  // OPTIONS is a CORS preflight and cannot carry Authorization; existing
833
854
  // handlers answer it themselves, so the gate does not swallow it.
834
- // A runtime-less tab mutation still needs authentication before the
835
- // dispatcher can return its 503. Do not enable this fallback for other paths.
836
- const runtimeLessTabMutation = !opts.metaApi && method !== 'GET' && method !== 'OPTIONS'
837
- && (pathname === '/v1/nexus/tabs' || pathname.startsWith('/v1/nexus/tabs/'));
855
+ // Without metaApi, only the owner bearer may reach these runtime-less
856
+ // meta-API stubs. All other private paths remain default-denied.
857
+ const runtimeLessMetaRoute = !opts.metaApi && (
858
+ (method !== 'GET' && method !== 'OPTIONS'
859
+ && (pathname === '/v1/nexus/tabs' || pathname.startsWith('/v1/nexus/tabs/')))
860
+ || (method === 'GET' && (
861
+ ['/v1/simulations', '/v1/tools', '/v1/intake', '/v1/sessions', '/v1/control-signals', '/v1/turns/last/screenshot'].includes(pathname)
862
+ || pathname.startsWith('/v1/recordings/')
863
+ ))
864
+ || (method === 'POST' && (
865
+ ['/v1/intake', '/v1/sessions/external', '/v1/control-signals', '/v1/prompt'].includes(pathname)
866
+ || pathname.startsWith('/v1/hitl/callback/')
867
+ ))
868
+ );
838
869
  if (
839
870
  method !== 'OPTIONS'
840
871
  && pathname.startsWith('/v1/')
841
872
  && !isPublicRoute(method, pathname, { setupMode: false })
842
873
  ) {
843
- const fallbackToken = runtimeLessTabMutation ? ensureAuthToken().token : undefined;
874
+ const fallbackToken = runtimeLessMetaRoute ? ensureAuthToken().token : undefined;
844
875
  const offered = req.headers.get('authorization');
845
876
  const fallbackAuthorized = fallbackToken !== undefined && offered?.startsWith('Bearer ') === true
846
877
  && compareTokenConstTime(offered.slice('Bearer '.length).trim(), fallbackToken);
@@ -916,12 +947,39 @@ export async function routeRequest(
916
947
  });
917
948
  }
918
949
 
950
+ if (pathname === SEAT_REQUESTS_PATH) {
951
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
952
+ return handleSeatRequests(req, opts.seatRequests);
953
+ }
954
+
955
+ if (pathname === CONSULT_REQUESTS_PATH) {
956
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
957
+ return handleConsultRequests(req, opts.consultRequests);
958
+ }
959
+
960
+ if (pathname === '/v1/exec-requests' || pathname.startsWith('/v1/exec-requests/')) {
961
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
962
+ if (/^\/v1\/exec-requests\/[^/]+\/files\//.test(pathname)) {
963
+ const credential = bearerCredential(req, opts.metaApi);
964
+ if (credential !== 'bearer-match' && credential !== 'temp-token') return jsonResponse({ error: 'unauthorized' }, 401);
965
+ }
966
+ return handleExecRequests(req, opts.execRequests ?? execRequestRunner());
967
+ }
968
+
919
969
  if (pathname === GRAPH_APPROVALS_PATH || pathname.startsWith(`${GRAPH_APPROVALS_PATH}/`)) {
920
970
  return handleGraphApprovals(req, {
921
971
  authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
922
972
  authReason: (request) => (opts.metaApi ? bearerCredential(request, opts.metaApi) : undefined),
923
973
  });
924
974
  }
975
+ // 현장 업로드(owner 전용) — GET 목록 · POST 저장. 저장 규칙 정본 = src/field/field-media.ts.
976
+ if ((pathname === FIELD_UPLOADS_PATH && (method === 'GET' || method === 'POST'))
977
+ || ((pathname === FIELD_REEL_PATH || pathname === FIELD_REEL_FILE_PATH) && method === 'GET')) {
978
+ return handleFieldUploads(req, {
979
+ authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
980
+ configuredEvent: () => { try { return getUserConfig().telegram?.fieldDefaultEvent; } catch { return undefined; } },
981
+ });
982
+ }
925
983
  if (pathname === APPROVALS_MERGES_PATH || pathname.startsWith(`${APPROVALS_MERGES_PATH}/`)) {
926
984
  return handleMergeApprovals(req, {
927
985
  authorize: (request) => !!opts.metaApi && checkAuth(request, opts.metaApi),
@@ -1208,6 +1266,11 @@ export async function routeRequest(
1208
1266
  return handleGraphsValidatePost(req, opts.metaApi);
1209
1267
  }
1210
1268
 
1269
+ if (pathname === '/v1/config/chat-fast-path' && (method === 'GET' || method === 'PUT')) {
1270
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1271
+ return handleChatFastPathConfig(req);
1272
+ }
1273
+
1211
1274
  // Mutation routes (PR ι) — POST/PATCH/DELETE on /v1/nexus/tabs[/:id[/action]].
1212
1275
  // Templates POST handled here too (PR κ).
1213
1276
  // Config / secrets PUT/POST/DELETE here too (PR μ).