elanous 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
  96. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/graphs/test-diet/recipes.yaml +9 -0
  178. package/graphs/test-diet/test-diet.yaml +22 -0
  179. package/package.json +6 -5
  180. package/persona-presets/_index.yaml +10 -0
  181. package/persona-presets/_schema.json +146 -0
  182. package/persona-presets/dana.yaml +26 -0
  183. package/persona-presets/haru.yaml +27 -0
  184. package/persona-presets/kai.yaml +27 -0
  185. package/persona-presets/leo.yaml +27 -0
  186. package/persona-presets/mira.yaml +30 -0
  187. package/persona-presets/nari.yaml +27 -0
  188. package/persona-presets/sora.yaml +27 -0
  189. package/persona-presets/yuna.yaml +27 -0
  190. package/plugins/card-followup/README.md +13 -4
  191. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  192. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  193. package/plugins/card-followup/graphs/run-step.ts +122 -18
  194. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  195. package/plugins/card-followup/plugin.json +1 -1
  196. package/plugins/doc-draft/README.md +13 -0
  197. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  198. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  199. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  200. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  201. package/plugins/doc-draft/plugin.json +24 -0
  202. package/plugins/geo-check/README.md +3 -1
  203. package/plugins/geo-check/graphs/run-step.ts +58 -13
  204. package/plugins/geo-check/plugin.json +1 -1
  205. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  206. package/plugins/job-coach/README.md +1 -1
  207. package/plugins/job-coach/graphs/run-step.ts +2 -2
  208. package/plugins/job-coach/plugin.json +1 -1
  209. package/plugins/job-coach/plugin.ts +1 -1
  210. package/scripts/affinity/endcard.sh +3 -2
  211. package/scripts/affinity/reframe.sh +3 -1
  212. package/scripts/ci-public-export-leak-gate.ts +48 -0
  213. package/scripts/codex-quota-alert.ts +122 -258
  214. package/scripts/coord-tracks.json +4 -4
  215. package/scripts/field-feed/deliver-node.ts +57 -0
  216. package/scripts/field-feed/feed-node.ts +67 -0
  217. package/scripts/field-feed/lib.ts +164 -0
  218. package/scripts/field-feed/reel-node.ts +24 -0
  219. package/scripts/field-feed/start.ts +62 -0
  220. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  221. package/scripts/install.sh +109 -21
  222. package/scripts/landing-heal/clean-checkout.ts +114 -0
  223. package/scripts/landing-heal/run-mission.ts +38 -3
  224. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  225. package/scripts/persona-presets/lint.ts +79 -0
  226. package/scripts/public-export.ts +8 -4
  227. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  228. package/scripts/release-loop/checklist-gate-node.ts +2 -2
  229. package/scripts/release-loop/env-known-failures.ts +31 -0
  230. package/scripts/release-loop/gate-node.ts +108 -22
  231. package/scripts/release-loop/known-issues-node.ts +159 -0
  232. package/scripts/release-loop/npm-publish-node.ts +124 -0
  233. package/scripts/release-loop/ops-upgrade-node.ts +95 -0
  234. package/scripts/release-loop/publish-node.ts +23 -1
  235. package/scripts/release-loop/release-readiness.ts +58 -0
  236. package/scripts/release-loop/shard-plan.ts +17 -0
  237. package/scripts/release-loop/unattended-release.ts +93 -0
  238. package/scripts/storyboard/storyboard.ts +167 -0
  239. package/scripts/test-diet/lib.ts +88 -0
  240. package/scripts/test-diet/node.ts +102 -0
  241. package/scripts/ux/measure-first-text.ts +102 -0
  242. package/scripts/vflow-collect.ts +20 -7
  243. package/scripts/vflow-search.ts +73 -0
  244. package/scripts/video-free-line-test-walker.ts +36 -0
  245. package/scripts/video-pipeline.ts +2 -2
  246. package/skills/explainer-video/SKILL.md +12 -9
  247. package/skills/explainer-video/engine/build.mjs +1 -1
  248. package/skills/explainer-video/engine/reel.mjs +138 -31
  249. package/skills/explainer-video/engine/reel.sh +2 -1
  250. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  251. package/skills/grill-me/SKILL.md +1 -1
  252. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  253. package/src/a2a/server.ts +2 -0
  254. package/src/acp/auth-methods.ts +30 -0
  255. package/src/acp/elanous-aware-peers.ts +30 -0
  256. package/src/acp/server.ts +96 -20
  257. package/src/agent/monad-agent-turn.ts +25 -6
  258. package/src/agent/shared-app-tools.ts +5 -3
  259. package/src/agent-substrate/review-intent.ts +6 -2
  260. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  261. package/src/auth/github-app-token.ts +4 -1
  262. package/src/autopilot/track-agent-shadow.ts +151 -0
  263. package/src/autopilot/track-agent.ts +23 -3
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/channel-import/detect.ts +191 -0
  271. package/src/channel-import/run.ts +111 -0
  272. package/src/chat/chat-cli.ts +4 -1
  273. package/src/chat/short-question.ts +40 -0
  274. package/src/cli/agent-cli.ts +24 -15
  275. package/src/cli/ask-bare.ts +42 -0
  276. package/src/cli/cli-user-error.ts +25 -0
  277. package/src/cli/codex-credit-plan-view.ts +25 -0
  278. package/src/cli/config-test-sync.ts +4 -0
  279. package/src/cli/decisions-cli.ts +13 -3
  280. package/src/cli/dependence-gauge.ts +237 -0
  281. package/src/cli/doctor-bundle.ts +128 -0
  282. package/src/cli/doctor-cli.ts +41 -1
  283. package/src/cli/doctor-local-llm.ts +95 -0
  284. package/src/cli/doctor-modes.ts +48 -0
  285. package/src/cli/import-cli.ts +60 -0
  286. package/src/cli/intake-cli.ts +12 -2
  287. package/src/cli/intake-source-cli.ts +58 -0
  288. package/src/cli/machine-cli.ts +23 -0
  289. package/src/cli/mcp-login.ts +13 -4
  290. package/src/cli/msg-cli.ts +137 -0
  291. package/src/cli/persona-cli.ts +116 -0
  292. package/src/cli/plugin-cli.ts +25 -0
  293. package/src/cli/pr-cli.ts +36 -0
  294. package/src/cli/provider-cli.ts +5 -0
  295. package/src/cli/release-cli.ts +50 -8
  296. package/src/cli/release-run-if-ready.ts +48 -0
  297. package/src/cli/setup-cli.ts +27 -6
  298. package/src/cli/setup-recommend.ts +8 -0
  299. package/src/cli/skills-cli.ts +173 -0
  300. package/src/cli/start-cli.ts +13 -4
  301. package/src/cli/usage-cli.ts +11 -1
  302. package/src/codex/setup.ts +37 -16
  303. package/src/dashboard/index.ts +18 -2
  304. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
  305. package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
  306. package/src/decisions/decision-cards.ts +291 -0
  307. package/src/decisions/decision-ledger.ts +5 -2
  308. package/src/decisions/discord-decision-cards.ts +133 -0
  309. package/src/decisions/telegram-decision-cards.ts +100 -0
  310. package/src/directives/version-at.ts +77 -0
  311. package/src/discord.ts +73 -6
  312. package/src/domains/core-tools.ts +37 -14
  313. package/src/domains/outbound-alert.ts +94 -12
  314. package/src/domains/repeated-failure.ts +1 -1
  315. package/src/error-report/report.ts +200 -0
  316. package/src/exec-requests/planner.ts +146 -0
  317. package/src/exec-requests/runner.ts +378 -0
  318. package/src/exec-requests/store.ts +78 -0
  319. package/src/field/field-feed-auto.ts +48 -0
  320. package/src/field/field-media.ts +8 -3
  321. package/src/field/field-reel.ts +203 -0
  322. package/src/grok/credential.ts +7 -5
  323. package/src/harness/aside-run-status.ts +33 -0
  324. package/src/harness/browser-verify.ts +48 -17
  325. package/src/harness/harness-cli-command.ts +25 -3
  326. package/src/harness/image-load-state.ts +21 -0
  327. package/src/harness/process-garbage.ts +45 -0
  328. package/src/hooks/error-report.ts +210 -0
  329. package/src/hooks/receiver.ts +41 -1
  330. package/src/import/apply.ts +128 -0
  331. package/src/import/plan.ts +197 -0
  332. package/src/index.ts +168 -21
  333. package/src/input-core/text-entry.ts +13 -5
  334. package/src/instance/leader.ts +13 -1
  335. package/src/intake-plane/collect-github.ts +5 -3
  336. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  337. package/src/intake-plane/discord-seat-work.ts +56 -0
  338. package/src/intake-plane/intake-sources.ts +202 -0
  339. package/src/intake-plane/items.ts +28 -12
  340. package/src/intake-plane/seat-answer.ts +96 -0
  341. package/src/intake-plane/seat-doc-route.ts +221 -0
  342. package/src/intake-plane/submit-intake-work.ts +9 -1
  343. package/src/intake-plane/telegram-seat-work.ts +72 -0
  344. package/src/llm/model-defaults.ts +24 -1
  345. package/src/llm.ts +5 -4
  346. package/src/machines/machine-ledger.ts +127 -0
  347. package/src/market/pack-internal-refs.ts +28 -0
  348. package/src/market/publish.ts +31 -4
  349. package/src/maturity/feature-maturity.ts +65 -0
  350. package/src/model-tier/turn-tier.ts +24 -0
  351. package/src/msg/msg-store.ts +149 -0
  352. package/src/native-tool-catalog.ts +2 -1
  353. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  354. package/src/nexus/api/consult-requests.ts +128 -0
  355. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  356. package/src/nexus/api/error-reports.ts +108 -0
  357. package/src/nexus/api/exec-requests.ts +51 -0
  358. package/src/nexus/api/field-uploads.ts +40 -3
  359. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  360. package/src/nexus/api/graph-approvals.ts +71 -4
  361. package/src/nexus/api/http-server.ts +82 -8
  362. package/src/nexus/api/outbound-report.ts +7 -0
  363. package/src/nexus/api/running-runs-background.ts +68 -0
  364. package/src/nexus/api/running-runs-child.ts +6 -0
  365. package/src/nexus/api/seat-requests.ts +254 -0
  366. package/src/nexus/api/share-captures.ts +91 -0
  367. package/src/nexus/api/skill-problems.ts +56 -0
  368. package/src/nexus/api/terminals.ts +24 -8
  369. package/src/nexus/api/trace-run-scan.ts +37 -0
  370. package/src/nexus/api/trace.ts +19 -3
  371. package/src/nexus/index.ts +28 -1
  372. package/src/nexus/setup-status.ts +11 -7
  373. package/src/notify/llm-budget-alert.ts +16 -10
  374. package/src/oauth/browser-availability.ts +14 -0
  375. package/src/oauth/codex-account-rotation.ts +26 -4
  376. package/src/oauth/codex-account-store.ts +20 -2
  377. package/src/oauth/codex-browser-login.ts +173 -0
  378. package/src/oauth/codex-quota-policy.ts +16 -0
  379. package/src/oauth/codex.ts +4 -1
  380. package/src/oauth/fallback-chain.ts +5 -5
  381. package/src/oauth/store.ts +42 -5
  382. package/src/onboarding/continue-here.ts +34 -0
  383. package/src/onboarding/entry-hints.ts +8 -0
  384. package/src/onboarding/full-screen-io.ts +35 -3
  385. package/src/onboarding/help/skills.md +1 -1
  386. package/src/onboarding/non-interactive.ts +1 -0
  387. package/src/onboarding/subscription-hint.ts +27 -0
  388. package/src/onboarding/web-first.ts +90 -0
  389. package/src/onboarding.ts +287 -26
  390. package/src/persona/presets.ts +105 -0
  391. package/src/plugins/maker/node-install.ts +92 -0
  392. package/src/plugins/maker/node-maker.ts +139 -0
  393. package/src/plugins/maker/plugin-maker.ts +19 -7
  394. package/src/policy/llm-policy.ts +268 -0
  395. package/src/prompt-library/session-guidance.ts +7 -1
  396. package/src/prompt-library/universal-preamble.ts +2 -2
  397. package/src/relay/relay-cli.ts +20 -0
  398. package/src/relay/relay-server.ts +220 -0
  399. package/src/release-loop/checklist.ts +37 -13
  400. package/src/self-dev/decompose.ts +1 -0
  401. package/src/self-dev/dev-pipeline.ts +6 -3
  402. package/src/self-dev/orchestrate.ts +18 -11
  403. package/src/self-dev/run-record-mirror.ts +44 -0
  404. package/src/self-dev/run-store.ts +12 -3
  405. package/src/self-implement/harness-policy-gates.ts +4 -1
  406. package/src/self-implement/report-origin.ts +12 -1
  407. package/src/session/chat.ts +17 -5
  408. package/src/shell-env-bootstrap.ts +44 -7
  409. package/src/skills/connect-claude-plugin.ts +142 -0
  410. package/src/skills/connect.ts +70 -0
  411. package/src/skills/index.ts +29 -1
  412. package/src/skills/repair.ts +124 -0
  413. package/src/skills/runner.ts +11 -2
  414. package/src/skills/shared-agent-skill-roots.ts +25 -0
  415. package/src/skills/trigger-extract.ts +1 -1
  416. package/src/steward/failure-streak.ts +78 -0
  417. package/src/steward/steward-cards.ts +24 -0
  418. package/src/steward/triage-budget.ts +127 -0
  419. package/src/steward/triage-plan.ts +75 -0
  420. package/src/steward/triage.ts +244 -42
  421. package/src/task-cards/card-store.ts +1 -1
  422. package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  424. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  426. package/src/telegram-commands.ts +13 -0
  427. package/src/telegram.ts +72 -2
  428. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  429. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  430. package/src/user-config.ts +189 -15
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/video-pipeline/recipes/character.ts +42 -5
  433. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  434. package/src/video-pipeline/recipes/free-line.ts +16 -3
  435. package/src/video-pipeline/recipes/remote.ts +7 -1
  436. package/src/video-pipeline/vflow-reference.ts +109 -0
  437. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  446. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  515. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  516. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  517. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
@@ -5,6 +5,7 @@
5
5
  // codex·elanous 자격 파일은 이 Job 에 없다. 격리 관문(호스트 넥서스에 닿으면 종료)은 기존 Job 과 같다.
6
6
  // 명령 인자는 셸 문자열로 이어붙이지 않고 `"$@"` 로 따로 인용한다.
7
7
 
8
+ import { randomBytes } from 'node:crypto';
8
9
  import { spawnSync } from 'node:child_process';
9
10
  import { isAbsolute } from 'node:path';
10
11
  import { getUserConfig } from '../../user-config.js';
@@ -66,6 +67,8 @@ if [ -d "$HOME/outbox" ]; then
66
67
  fi`;
67
68
 
68
69
  export interface PodCommandJobInput {
70
+ /** POD1 — this launch's own label; cleanup selects by it, never by name alone. */
71
+ launch?: string;
69
72
  name: string;
70
73
  namespace: string;
71
74
  image: string;
@@ -122,13 +125,13 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
122
125
  return {
123
126
  apiVersion: 'batch/v1',
124
127
  kind: 'Job',
125
- metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command' } },
128
+ metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
126
129
  spec: {
127
130
  backoffLimit: 0,
128
131
  ttlSecondsAfterFinished: 7200,
129
132
  activeDeadlineSeconds: o.deadlineSeconds,
130
133
  template: {
131
- metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command' } },
134
+ metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
132
135
  spec: {
133
136
  restartPolicy: 'Never',
134
137
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
@@ -166,6 +169,7 @@ export function commandJobSecret(o: {
166
169
  skillEnvText: Readonly<Record<string, string>>;
167
170
  grokAuth?: string;
168
171
  ghToken?: string;
172
+ launch?: string;
169
173
  }): Record<string, unknown> | null {
170
174
  const stringData: Record<string, string> = {};
171
175
  if (o.grokAuth !== undefined) stringData['grok-auth.json'] = o.grokAuth;
@@ -177,7 +181,7 @@ export function commandJobSecret(o: {
177
181
  if (Object.keys(stringData).length === 0) return null;
178
182
  return {
179
183
  apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
180
- metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name } },
184
+ metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name, ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
181
185
  stringData,
182
186
  };
183
187
  }
@@ -265,7 +269,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
265
269
  const image = options.image ?? 'elanous-harness:local';
266
270
  const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/elanous';
267
271
  const deadlineSeconds = options.deadlineSeconds ?? POD_COMMAND_DEADLINE_SECONDS;
268
- const name = options.name ?? podJobName(`cmd-${Date.now().toString(36)}`);
272
+ // POD1 (10-01): two launches in the same moment got the same `si-cmd-<time>` and the failing one's cleanup
273
+ // deleted the other's credentials. The name now has a random tail, and cleanup selects this launch's label.
274
+ const launch = randomBytes(6).toString('hex');
275
+ const name = options.name ?? podJobName(`cmd-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`);
276
+ const own = `elanous.job=${name},elanous.launch=${launch}`;
269
277
  const env = options.env ?? process.env;
270
278
  const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR
271
279
  ?? (options.configHostMirror ?? (() => getUserConfig().pod?.hostMirror))())?.trim() || undefined;
@@ -293,7 +301,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
293
301
  const currentContext = member || options.kubectl ? null : baseKubectl(['config', 'current-context']);
294
302
  const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
295
303
  const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
296
- const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
304
+ const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
297
305
  const artifactsDir = `${options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`}/${name}`;
298
306
  try {
299
307
  if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
@@ -328,19 +336,19 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
328
336
  if (synced?.imageRef) member = { ...member, imageRef: synced.imageRef };
329
337
  }
330
338
  const ghToken = options.clone ? (options.ghToken ?? defaultGhToken)() : undefined;
331
- const secret = commandJobSecret({ name, namespace, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
339
+ const secret = commandJobSecret({ name, namespace, launch, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
332
340
  if (secret) {
333
341
  const applied = kubectl(['apply', '-f', '-'], JSON.stringify(secret));
334
342
  if (applied.status !== 0) throw new Error(applied.stderr.trim() || 'pod command: Secret 적용 실패');
335
343
  }
336
344
  const manifest = podCommandJobManifest({
337
- name, namespace, image: member?.imageRef ?? image,
345
+ name, namespace, launch, image: member?.imageRef ?? image,
338
346
  ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
339
347
  repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
340
348
  ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(options.memoryLimit ? { memoryLimit: options.memoryLimit } : {}), deadlineSeconds,
341
349
  ...(options.runId ? { runId: options.runId } : {}),
342
350
  });
343
- kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found']);
351
+ kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
344
352
  const applied = kubectl(['apply', '-f', '-'], JSON.stringify(manifest));
345
353
  if (applied.status !== 0) {
346
354
  cleanupSecret();
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } fr
2
2
  import { gunzipSync } from 'node:zlib';
3
3
  import { debug } from '../../debug/log.js';
4
4
  import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
5
+ import { ledgerLineToLogEvent } from './pod-ledger-events.js';
5
6
 
6
7
  type Ledger = { runId: string; jsonl: string };
7
8
  type Incomplete = { runId: string; reason: string };
@@ -124,7 +125,7 @@ export function createPodLedgerFollower(opts: {
124
125
  let owned = false;
125
126
  let disabled = false;
126
127
  let lastProgressAt = now();
127
- let lastProgressEvent: string | null = null;
128
+ let visibleProgressEvent: string | null = null;
128
129
  let lastActivityMeasuredAt = lastProgressAt;
129
130
  let lastActivityLogAt = -Infinity;
130
131
  let nextStallMinute = stallMinutes;
@@ -132,11 +133,11 @@ export function createPodLedgerFollower(opts: {
132
133
  function checkStall(ledgerRead: boolean, activityMeasured: boolean) {
133
134
  const idleMinutes = Math.max(0, (now() - lastProgressAt) / 60_000);
134
135
  if (idleMinutes >= nextStallMinute) {
135
- log('self-implement.pod', 'stalled', { runId: opts.runId, lastProgressEvent, idleMinutes });
136
+ log('self-implement.pod', 'stalled', { runId: opts.runId, lastProgressEvent: visibleProgressEvent, idleMinutes });
136
137
  const observation = ledgerRead
137
138
  ? (activityMeasured ? '원장·작업 트리 모두 조용함' : '원장 조용함')
138
139
  : '원장 못 잼';
139
- onStall(`[pod] 진행 없음 ${Math.floor(idleMinutes)}분 — ${observation} · 마지막 진행 ${lastProgressEvent ?? '없음'}${activityMeasured ? '' : ' (작업 트리 못 잼)'}`);
140
+ onStall(`[pod] 진행 없음 ${Math.floor(idleMinutes)}분 — ${observation} · 마지막 진행 ${visibleProgressEvent ?? '없음'}${activityMeasured ? '' : ' (작업 트리 못 잼)'}`);
140
141
  stalled = true;
141
142
  nextStallMinute = (Math.floor(idleMinutes / stallMinutes) + 1) * stallMinutes;
142
143
  }
@@ -169,7 +170,7 @@ export function createPodLedgerFollower(opts: {
169
170
  if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
170
171
  stalled = false;
171
172
  lastProgressAt = now();
172
- lastProgressEvent = 'worktree-activity';
173
+ visibleProgressEvent = 'worktree-activity';
173
174
  nextStallMinute = stallMinutes;
174
175
  if (now() - lastActivityLogAt >= 5 * 60_000) {
175
176
  log('self-implement.pod', 'worktree-activity', { runId: opts.runId, files });
@@ -190,6 +191,11 @@ export function createPodLedgerFollower(opts: {
190
191
  log('self-implement.pod', 'ledger-live-appended', { runId: opts.runId, lines: complete.split('\n').filter(Boolean).length, bytes: Buffer.byteLength(complete), offset });
191
192
  for (const line of complete.split('\n')) {
192
193
  if (!line) continue;
194
+ let logEvent: ReturnType<typeof ledgerLineToLogEvent>;
195
+ try {
196
+ logEvent = ledgerLineToLogEvent(line, opts.runId);
197
+ if (logEvent) log(logEvent.category, logEvent.event, logEvent.data);
198
+ } catch { /* A failed host log write must not interrupt ledger following or stall accounting. */ }
193
199
  let entry: unknown;
194
200
  try { entry = JSON.parse(line); } catch { continue; }
195
201
  const event = (entry && typeof entry === 'object' && 'event' in entry) ? entry.event : undefined;
@@ -198,7 +204,7 @@ export function createPodLedgerFollower(opts: {
198
204
  if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
199
205
  stalled = false;
200
206
  lastProgressAt = now();
201
- lastProgressEvent = event;
207
+ visibleProgressEvent = logEvent?.event ?? '[redacted]';
202
208
  nextStallMinute = stallMinutes;
203
209
  }
204
210
  checkStall(true, activityMeasured);
@@ -0,0 +1,68 @@
1
+ const SECRET_FIELD = /token|secret|password|authorization|key/i;
2
+ const SAFE_FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
3
+ const SECRET_VALUE = /(?:\b(?:bearer|basic)\s+\S+|\b(?:token|secret|password|authorization|api[-_]?key|access[-_]?key|private[-_]?key)\b\s*(?:is|[=:])\s*["']?\S+|\b(?:sk-[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9_]{8,}))\b/i;
4
+ const MAX_FIELDS = 32;
5
+ // Review R2: only lifecycle events the host needs to see, and no free text. Event names must start with a known
6
+ // lifecycle prefix; string fields survive only when they are short code-like values in a fixed set of keys.
7
+ const EVENT_ALLOWED = /^(?:gate|gated|review|reviewed|repair|repaired|rework|merge|merged|pr|land|landed|stage|round|supervisor|child|plan|planned|decompose|decomposed|shard|stall|harvest|decision|run|pipeline|progress|human|start|started|finish|finished|done|implement|implemented|commit|test|typecheck)(?:-(?:started|finished|round|opened|closed|failed|passed|blocked|done|entry|node|outcome|delivery|decision|stop|merged|ready|requested|resolved|result|fix|must-fix|verdict|skipped|origin|cleared))*$/;
8
+ const CODE_FIELDS = new Set(['stage', 'outcome', 'verdict', 'status', 'kind', 'phase', 'role', 'result']);
9
+ const CODE_VALUE = /^[a-z0-9_.:-]{1,40}$/i;
10
+
11
+ function secretValues(root: Record<string, unknown>): string[] {
12
+ const secrets: string[] = [];
13
+ const pending: unknown[] = [root];
14
+ while (pending.length) {
15
+ const current = pending.pop();
16
+ if (!current || typeof current !== 'object') continue;
17
+ if (Array.isArray(current)) {
18
+ pending.push(...current);
19
+ continue;
20
+ }
21
+ for (const [name, value] of Object.entries(current)) {
22
+ if (SECRET_FIELD.test(name) && typeof value === 'string' && value.length >= 3) secrets.push(value);
23
+ if (value && typeof value === 'object') pending.push(value);
24
+ }
25
+ }
26
+ return secrets;
27
+ }
28
+
29
+ /** Project bounded ledger metadata, excluding named secrets and recognizable credential values. */
30
+ export function ledgerLineToLogEvent(line: string, runId: string): {
31
+ category: string;
32
+ event: string;
33
+ data: Record<string, unknown>;
34
+ } | undefined {
35
+ let entry: unknown;
36
+ try { entry = JSON.parse(line); } catch { return undefined; }
37
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !('event' in entry) || typeof entry.event !== 'string') return undefined;
38
+ // The real run-ledger writer wraps observer fields in `data`; older/synthetic lines can be flat.
39
+ const details = 'data' in entry && entry.data && typeof entry.data === 'object' && !Array.isArray(entry.data)
40
+ ? entry.data as Record<string, unknown> : undefined;
41
+ const sources = [entry, details].filter((source): source is Record<string, unknown> => source !== undefined);
42
+ const knownSecrets = secretValues(entry);
43
+ const containsCredential = (value: string) => SECRET_VALUE.test(value) || knownSecrets.some((secret) => value.includes(secret));
44
+ if (containsCredential(entry.event) || !EVENT_ALLOWED.test(entry.event)) return undefined;
45
+
46
+ const data: Record<string, unknown> = { runId };
47
+ let fields = 0;
48
+ for (const source of sources) {
49
+ for (const [name, value] of Object.entries(source)) {
50
+ if (name === 'event' || name === 'runId' || name === 'data' || !SAFE_FIELD.test(name) || SECRET_FIELD.test(name)) continue;
51
+ if (fields >= MAX_FIELDS) break;
52
+ const countName = `${name}Count`;
53
+ const outputName = Array.isArray(value) || value && typeof value === 'object' ? countName : name;
54
+ if (!SAFE_FIELD.test(outputName) || SECRET_FIELD.test(outputName) || Object.hasOwn(data, outputName)) continue;
55
+ if (typeof value === 'string') {
56
+ // Free text (reasons, messages, patches) never leaves the pod ledger; only short code values in known keys.
57
+ if (!CODE_FIELDS.has(name) || !CODE_VALUE.test(value) || containsCredential(value)) continue;
58
+ data[name] = value;
59
+ } else if (typeof value === 'number' && Number.isFinite(value) || typeof value === 'boolean' || value === null) {
60
+ data[name] = value;
61
+ } else if (Array.isArray(value) || value && typeof value === 'object') {
62
+ data[countName] = Array.isArray(value) ? value.length : Object.keys(value).length;
63
+ } else continue;
64
+ fields += 1;
65
+ }
66
+ }
67
+ return { category: 'self-implement.pod.ledger', event: entry.event, data };
68
+ }
@@ -50,7 +50,7 @@ import { findGitDir } from '../../git-fs/locate.js';
50
50
  import { debug } from '../../debug/log.js';
51
51
  import { selectLiveDetail } from '../../live/detail-switch.js';
52
52
  import { authStorePath } from '../../oauth/store.js';
53
- import { grokAuthFilePath, resolveGrokCredential } from '../../grok/credential.js';
53
+ import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
54
54
  import { defaultGrokModel } from '../../grok/models.js';
55
55
  import { resolveHostId } from '../../platform/host-id.js';
56
56
  import { envLiteral } from '../../platform/env-literal.js';
@@ -171,12 +171,26 @@ export function hostCredentials(account: string, storePath: string = authStorePa
171
171
  return { elanousAuth, codexAuth, ghToken: ghToken() };
172
172
  }
173
173
 
174
- /** resolveGrokCredential 과 동일한 출처를 읽되 Pod 로는 refresh 없는 access 사본만 보낸다. */
174
+ /** A Pod gets a refresh-less access copy, so the copy must outlive the Job: same rule as the Codex path (3 h). */
175
+ export const POD_GROK_MIN_VALIDITY_MS = 3 * 3600_000;
176
+
177
+ /** resolveGrokCredential 과 동일한 출처를 읽되 Pod 로는 refresh 없는 access 사본만 보낸다.
178
+ * POD4 (10-01): the copy used to ship whatever the host had — an AUTH1 shard copied a token that had expired
179
+ * 4 minutes earlier and died at `grok-credential-preflight status=expired`. Refresh on the host first. */
175
180
  export function hostGrokCredentials(opts: {
176
181
  home?: string; env?: NodeJS.ProcessEnv; apiKeyOptIn?: boolean; ghToken?: () => string;
182
+ /** Test seams — the host refresh (read-only `grok models`) and the expiry probe. */
183
+ refresh?: () => void; isExpiring?: () => boolean;
177
184
  } = {}): { grokAuth?: string; grokApiKey?: string; ghToken: string } {
178
- const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
185
+ let credential = resolveGrokCredential({ home: opts.home, env: opts.env });
179
186
  if (credential?.kind === 'subscription') {
187
+ const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS }));
188
+ if (expiring()) {
189
+ try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { /* judged below */ }
190
+ if (expiring()) throw new Error('grok: 구독 access 토큰이 3시간 안에 만료 — 호스트 갱신(grok models)도 못 늘렸다 · 호스트에서 grok login 후 다시');
191
+ credential = resolveGrokCredential({ home: opts.home, env: opts.env });
192
+ if (credential?.kind !== 'subscription') throw new Error('grok: 갱신 뒤 구독 자격을 다시 읽지 못했다');
193
+ }
180
194
  const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, unknown>;
181
195
  const redacted: Record<string, { key: string; expires_at?: string; user_id?: string }> = {};
182
196
  for (const [name, value] of Object.entries(scopes)) {
@@ -219,6 +233,64 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
219
233
 
220
234
  /** Job 스크립트의 salvage 단계 — 자식 rc 가 0 이 아닐 때만, 변경·미푸시 커밋이 있는 격리 워크트리를 `salvage/<job>/<worktree>` 로 push.
221
235
  * main·self-impl/ 로는 절대 안 민다. 실패해도 제어 흐름을 바꾸지 않는다(호출자는 이 뒤에 `exit $rc`). */
236
+ /**
237
+ * App installation tokens live one hour and only the host process renews them (at 10 minutes left).
238
+ * If that process dies (host reboot), nothing renews the Pod's token. hosts.yml unchanged for this long
239
+ * means the host is gone. Push a snapshot while the token still works.
240
+ */
241
+ export const POD_GH_STALE_SECONDS = 53 * 60;
242
+
243
+ /** Snapshot every worktree to salvage/<job>/<wt>-early without touching the child's index, worktree or HEAD. */
244
+ export function podEarlySalvageScript(): string {
245
+ return [
246
+ 'job_name="${ELANOUS_POD_NAME:-unknown-job}"',
247
+ 'while IFS= read -r wt; do',
248
+ ' [ -n "$wt" ] || continue',
249
+ ' case "$wt" in /*) ;; *) continue ;; esac',
250
+ ' wt_name=$(basename -- "$wt")',
251
+ " case \"$wt_name\" in ''|.*|*/*|*'..'*) continue ;; esac",
252
+ ' branch="salvage/${job_name}/${wt_name}-early"',
253
+ ' (',
254
+ ' set +e',
255
+ ' cd -- "$wt" || exit 0',
256
+ ' head=$(git rev-parse --verify -q HEAD) || { printf \'ELANOUS_POD_SALVAGE_NONE early-no-head\\n\'; exit 0; }',
257
+ ' idx=$(mktemp) || exit 0',
258
+ ' cp -- "$(git rev-parse --git-path index)" "$idx" 2>/dev/null || :',
259
+ ' GIT_INDEX_FILE="$idx" git add -A -- . >/dev/null 2>&1',
260
+ ' tree=$(GIT_INDEX_FILE="$idx" git write-tree 2>/dev/null)',
261
+ ' rm -f -- "$idx"',
262
+ ' [ -n "$tree" ] || { printf \'ELANOUS_POD_SALVAGE_NONE early-write-tree-failed\\n\'; exit 0; }',
263
+ ' base=$(git rev-parse --verify -q origin/HEAD || git rev-parse --verify -q origin/main || true)',
264
+ ' if [ "$tree" = "$(git rev-parse "${head}^{tree}")" ]; then',
265
+ ' if [ -z "$base" ] || [ "$(git rev-list --count "${base}..${head}" 2>/dev/null || echo 1)" = 0 ]; then printf \'ELANOUS_POD_SALVAGE_NONE clean\\n\'; exit 0; fi',
266
+ ' commit=$head',
267
+ ' else',
268
+ ' commit=$(git commit-tree "$tree" -p "$head" -m "salvage(early): ${job_name} host token refresh stopped") || { printf \'ELANOUS_POD_SALVAGE_NONE commit-failed\\n\'; exit 0; }',
269
+ ' fi',
270
+ ' git push --force origin "${commit}:refs/heads/${branch}" || { printf \'ELANOUS_POD_SALVAGE_NONE push-failed\\n\'; exit 0; }',
271
+ " printf 'ELANOUS_POD_SALVAGE %s %s\\n' \"$branch\" \"$commit\"",
272
+ ' )',
273
+ "done < <(git worktree list --porcelain 2>/dev/null | awk '/^worktree / { sub(/^worktree /, \"\"); print }')",
274
+ ].join('\n');
275
+ }
276
+
277
+ /** Background watchdog: fires the early snapshot once when hosts.yml has not been rewritten for staleSeconds. */
278
+ export function podGithubWatchdogScript(staleSeconds: number, intervalSeconds = 30): string {
279
+ return [
280
+ '(',
281
+ ` while sleep ${intervalSeconds}; do`,
282
+ ' f="$GH_CONFIG_DIR/hosts.yml"',
283
+ ' m=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null) || continue',
284
+ ' age=$(( $(date +%s) - m ))',
285
+ ` [ "$age" -ge ${staleSeconds} ] || continue`,
286
+ " printf 'ELANOUS_POD_GH_STALE %s\\n' \"$age\"",
287
+ ` ${podEarlySalvageScript().split('\n').join('\n ')}`,
288
+ ' break',
289
+ ' done',
290
+ ') & gh_watch_pid=$!',
291
+ ].join('\n');
292
+ }
293
+
222
294
  export function podSalvageScript(): string {
223
295
  return [
224
296
  'if [ "${rc:-0}" -ne 0 ]; then',
@@ -368,7 +440,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
368
440
  return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
369
441
  }
370
442
 
371
- export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean }): Record<string, unknown> {
443
+ export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number }): Record<string, unknown> {
372
444
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
373
445
  const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
374
446
  const askBaseIndex = o.goalDoc ? o.args.indexOf('--base') : -1;
@@ -394,6 +466,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
394
466
  ] : []),
395
467
  // Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
396
468
  `(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
469
+ ...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
397
470
  // 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
398
471
  goalPath
399
472
  ? `elanous harness ask ${goalPath} --json${quotedAsk} > /tmp/si.out 2>&1; rc=$?; bun -e 'const fs=require("fs");const p="/tmp/si.out";const s=fs.readFileSync(p,"utf8");const line=s.trimEnd().split("\\n").at(-1);try{const o=JSON.parse(line);if(o.kind==="self"&&o.result&&typeof o.result==="object")fs.appendFileSync(p,"\\n"+JSON.stringify({...o.result,...(typeof o.ok==="boolean"?{ok:o.ok}:{})})+"\\n")}catch{}'`
@@ -454,6 +527,7 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
454
527
  done
455
528
  if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
456
529
  'kill "$mem_sampler_pid" 2>/dev/null || true',
530
+ ...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
457
531
  'wait "$mem_sampler_pid" 2>/dev/null || true',
458
532
  'tail -n 1 /tmp/si.out',
459
533
  podSalvageScript(),
@@ -762,6 +762,19 @@ export function defaultTelegramCommands(): TgSlashCommand[] {
762
762
  });
763
763
  },
764
764
  },
765
+ {
766
+ name: 'decisions',
767
+ description: '열린 결정 목록 — 카드를 다시 보내 여기서 바로 정할 수 있습니다',
768
+ handler: async (_args, ctx) => {
769
+ const { telegramDecisionService } = await import('./decisions/telegram-decision-cards.js');
770
+ const service = telegramDecisionService();
771
+ if (!service) return '결정 카드가 이 봇에 연결되어 있지 않습니다.';
772
+ if (!service.isOwner(String(ctx.userId))) return '결정은 소유자만 볼 수 있습니다.';
773
+ // A group would show every member the cards (and route memos from them) — private chat only (TC review #22660).
774
+ if (ctx.chatId !== ctx.userId) return '결정은 개인 대화에서만 볼 수 있습니다.';
775
+ return service.listOpen(String(ctx.chatId));
776
+ },
777
+ },
765
778
  {
766
779
  name: 'work',
767
780
  description: '글을 알맞은 곳(흡수·태스크·하니스)에 넣고 결과를 여기로 답합니다 — /work [absorb|tasks|graph] <글>',
package/src/telegram.ts CHANGED
@@ -66,9 +66,11 @@ import { observeDevRequestRouteFailSoft } from './skills/dev-request-router.js';
66
66
  import { runUrlRoute } from './skills/url-route-exec.js';
67
67
  import { recordRoutedLinkAbsorbed } from './intake-plane/link-ledger.js';
68
68
  import { classifyFrontInput } from './intake-plane/front-classifier.js';
69
+ import { handleTelegramSeatWork, type TelegramSeatWorkDeps } from './intake-plane/telegram-seat-work.js';
69
70
  import { effectiveInstanceRoot } from './instance/resolve.js';
70
71
  import { getElanousConfigDir } from './elanous-config-dir.js';
71
72
  import { defaultFieldEvent, FieldUploadError, parseFieldCaption, resolveFieldMime, saveFieldMedia } from './field/field-media.js';
73
+ import { scheduleFieldReel, type FieldReelOptions } from './field/field-reel.js';
72
74
 
73
75
  export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
74
76
 
@@ -206,10 +208,14 @@ export interface TelegramBotOpts {
206
208
  * independently of the LLM chat path. Best-effort: errors are
207
209
  * swallowed by the caller — never blocks chat reply. */
208
210
  onTriggerTap?: (event: TgTriggerEvent) => void | Promise<void>;
211
+ /** Test seam for addressed seat intake; defaults to the real intake door. */
212
+ seatWorkDeps?: TelegramSeatWorkDeps;
209
213
  /** `#현장` 기본 행사 — 없으면 `slashContext.userConfig.telegram.fieldDefaultEvent` · 그것도 없으면 `field-<로컬 날짜>`. */
210
214
  fieldDefaultEvent?: string;
211
215
  /** 시험 seam — 현장 폴더 뿌리(기본 `getElanousConfigDir()`). */
212
216
  fieldRootDir?: () => string;
217
+ /** 시험에서는 실제 렌더 대신 가짜 실행기를 주입한다. */
218
+ fieldReel?: FieldReelOptions;
213
219
  }
214
220
 
215
221
  /** Surface-unification v2 (FU-2) — Telegram trigger event passed to
@@ -326,6 +332,8 @@ export class TelegramBot {
326
332
  private readonly globalWindow: number[] = [];
327
333
  private readonly fieldDefaultEventOpt?: string;
328
334
  private readonly fieldRootDir: () => string;
335
+ private readonly fieldReel?: FieldReelOptions;
336
+ private readonly seatWorkDeps: TelegramSeatWorkDeps;
329
337
  /** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
330
338
  private readonly fieldGroups = new Map<string, {
331
339
  event: string; at: number; chatId: number; threadId?: number; replyTo: number; count: number;
@@ -359,6 +367,8 @@ export class TelegramBot {
359
367
  this.voiceAdapter = opts.voiceAdapter;
360
368
  this.fieldDefaultEventOpt = opts.fieldDefaultEvent;
361
369
  this.fieldRootDir = opts.fieldRootDir ?? getElanousConfigDir;
370
+ this.fieldReel = opts.fieldReel;
371
+ this.seatWorkDeps = opts.seatWorkDeps ?? {};
362
372
  if (opts.onTriggerTap) {
363
373
  // Assign through `as unknown` to bypass `readonly` + private —
364
374
  // the tap is set once at construction and never reassigned.
@@ -796,6 +806,7 @@ export class TelegramBot {
796
806
  replyTo?: number;
797
807
  threadId?: number;
798
808
  caption?: string;
809
+ mimeType?: string;
799
810
  } = {}): Promise<{ messageId: number } | undefined> {
800
811
  // Per-chat gap mirror — keep telegram happy on rapid document spills.
801
812
  const last = this.chatLastSentAt.get(chatId) ?? 0;
@@ -816,7 +827,7 @@ export class TelegramBot {
816
827
  new Uint8Array(blobBytes).set(bytes);
817
828
  form.set(
818
829
  'document',
819
- new Blob([blobBytes], { type: 'text/plain' }),
830
+ new Blob([blobBytes], { type: opts.mimeType ?? 'text/plain' }),
820
831
  filename,
821
832
  );
822
833
 
@@ -908,6 +919,24 @@ export class TelegramBot {
908
919
  return undefined;
909
920
  }
910
921
 
922
+ /** DEC-TG — replace a message's text AND its inline keyboard (empty rows remove the buttons). */
923
+ async editMessageWithKeyboard(
924
+ chatId: number,
925
+ messageId: number,
926
+ text: string,
927
+ buttons: Array<Array<{ text: string; data: string }>>,
928
+ ): Promise<void> {
929
+ await this.throttledCall<unknown>(chatId, 'editMessageText', {
930
+ chat_id: chatId,
931
+ message_id: messageId,
932
+ text: text.length > this.maxChars ? `${text.slice(0, this.maxChars - 2)} …` : text,
933
+ reply_markup: { inline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.text, callback_data: b.data.slice(0, 64) }))) },
934
+ });
935
+ }
936
+
937
+ /** True while the long-poll loop runs — only a polling bot should push decision cards (nexus may hold a send-only bot). */
938
+ isPolling(): boolean { return this.running; }
939
+
911
940
  /** T2-P6 — acknowledge a callback_query so Telegram clears the
912
941
  * spinner next to the button the user tapped. Pass a `text` to
913
942
  * flash a tiny toast on the user's device (≤200 chars). Returns
@@ -1145,6 +1174,8 @@ export class TelegramBot {
1145
1174
  if (u.callback_query) {
1146
1175
  const cq = u.callback_query;
1147
1176
  if (!this.isOwnerAllowed(cq.from.id)) {
1177
+ // DEC-TG — a decision-card tap from someone else is the rehearsal's «refused» case; name it there too.
1178
+ if ((cq.data ?? '').startsWith('dec:')) debug.log('decisions.telegram', 'tap-refused', { platform: 'telegram', gate: 'owner-allowlist' }, { level: 'warn' });
1148
1179
  debug.log('telegram.owner-gate', 'refused', {
1149
1180
  kind: 'callback', reason: this.allowedUsers.size === 0 ? 'empty-allowlist' : 'not-allowed', userId: cq.from.id,
1150
1181
  });
@@ -1402,7 +1433,26 @@ export class TelegramBot {
1402
1433
  }
1403
1434
  }
1404
1435
  // 글(자막)은 캡션이 달린 항목에만 있다 — 앨범이면 첫 장에만 붙는다(MK 2026-09-30).
1405
- count = saveFieldMedia(inputs, { rootDir: this.fieldRootDir(), event, device: 'telegram', ...(tag?.text ? { caption: tag.text } : {}) }).count;
1436
+ const saved = saveFieldMedia(inputs, { rootDir: this.fieldRootDir(), event, device: 'telegram', ...(tag?.text ? { caption: tag.text } : {}) });
1437
+ count = saved.count;
1438
+ scheduleFieldReel(saved.dir, {
1439
+ ...this.fieldReel,
1440
+ notificationKey: `telegram:${this.botId}:${ctx.chatId}:${ctx.threadId ?? ''}:${ctx.mediaGroupId ?? ctx.messageId}`,
1441
+ onDone: async (result) => {
1442
+ const reply = { replyTo: ctx.messageId, threadId: ctx.threadId };
1443
+ if (result.ok && result.file) {
1444
+ const sent = await this.sendDocument(ctx.chatId, readFileSync(result.file), 'reel-9x16.mp4', {
1445
+ ...reply, mimeType: 'video/mp4', caption: `현장 영상 · ${event} · ${result.seconds}초`,
1446
+ });
1447
+ if (!sent) throw new Error('sendDocument failed');
1448
+ if (result.feed?.started) {
1449
+ await this.sendMessage(ctx.chatId, `인스타 피드 초안을 만드는 중 · ${event} — 1분쯤 뒤 앱 «게시 대기»에서 고치고 «최종 게시»를 누르면 됩니다(누르기 전엔 아무 데도 안 올라갑니다).`, reply);
1450
+ }
1451
+ } else {
1452
+ await this.sendMessage(ctx.chatId, `현장 영상 실패 · ${event} · ${result.error ?? 'render failed'}`, reply);
1453
+ }
1454
+ },
1455
+ });
1406
1456
  debug.log('field.upload', 'saved', { surface: 'telegram', files: inputs.length, count, album: !!ctx.mediaGroupId });
1407
1457
  } catch (err) {
1408
1458
  failure = err instanceof FieldUploadError ? err.code : 'download-failed';
@@ -1440,6 +1490,15 @@ export class TelegramBot {
1440
1490
  }
1441
1491
  // 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
1442
1492
  if (await this.tryHandleFieldUpload(ctx)) return;
1493
+ if (ctx.text.startsWith('@')) {
1494
+ const reply = await handleTelegramSeatWork(ctx.text, {
1495
+ chatId: ctx.chatId, messageId: ctx.messageId, threadId: ctx.threadId, botId: ctx.botId,
1496
+ }, this.seatWorkDeps);
1497
+ if (reply !== null) {
1498
+ await this.sendMessage(ctx.chatId, reply, { replyTo: ctx.messageId, threadId: ctx.threadId });
1499
+ return;
1500
+ }
1501
+ }
1443
1502
  // 영상은 `#현장` 저장 전용으로만 파싱한다 — 그 밖의 영상은 종전처럼 무시한다.
1444
1503
  if (ctx.video && ctx.attachments.length === 0) return;
1445
1504
  // Cascade-zyu U2 — capture utterance intent at update ingress.
@@ -2804,6 +2863,17 @@ export function botFromConfig(opts: BotFromConfigOpts): TelegramBot {
2804
2863
  // poll loop requests `callback_query` from the first getUpdates —
2805
2864
  // in-turn approval taps then arrive without a mid-turn delay.
2806
2865
  botRef.ensureSurfaceHitl();
2866
+ // DEC-TG(대표 10-02) — decision cards on the MAIN bot only (other channel pollers carry other tokens). The ticker
2867
+ // itself sends only while this bot polls, so a send-only nexus bot never duplicates the standalone poller's cards.
2868
+ if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
2869
+ const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
2870
+ if (mainToken && botToken === mainToken) {
2871
+ const bot = botRef;
2872
+ void import('./decisions/telegram-decision-cards.js')
2873
+ .then(({ attachTelegramDecisionCards }) => { attachTelegramDecisionCards(bot, opts.userConfig); })
2874
+ .catch((err: unknown) => log(`[telegram] decision cards not attached: ${err instanceof Error ? err.message : String(err)}`));
2875
+ }
2876
+ }
2807
2877
  return botRef;
2808
2878
  }
2809
2879
 
@@ -2,7 +2,7 @@
2
2
  // `elanous_skills_list` MCP tool. Mirrors the daemon-side ACP method
3
3
  // `elanous/skills/list` (src/acp/server.ts:2241) but session-agnostic so
4
4
  // the codex app-server can call it through the elanous-tools MCP server
5
- // without first opening an ACP session. Enumerates `~/.elanous/skills/*`
5
+ // without first opening an ACP session. Enumerates configured skill roots
6
6
  // and surfaces the first non-heading line of each SKILL.md as the
7
7
  // description.
8
8
  //
@@ -14,6 +14,7 @@
14
14
  import { join } from 'node:path';
15
15
  import { readdir, readFile } from 'node:fs/promises';
16
16
  import { defaultSkillDirs } from '../user-config.js';
17
+ import { SKILLS_LIST_DESCRIPTION } from '../onboarding/entry-hints.js';
17
18
  import type { LLMToolSpec } from '../llm.js';
18
19
  import type { ToolRuntime } from './types.js';
19
20
 
@@ -41,8 +42,7 @@ export interface ElanousSkillsListResult extends Record<string, unknown> {
41
42
  export function buildElanousSkillsListTool(): LLMToolSpec {
42
43
  return {
43
44
  name: 'elanous_skills_list',
44
- description:
45
- 'List installed elanous skills (sub-directories of ~/.elanous/skills with optional SKILL.md). Codex app-server callback via the elanous-tools MCP server. Read-only.',
45
+ description: SKILLS_LIST_DESCRIPTION,
46
46
  parameters: {
47
47
  type: 'object',
48
48
  properties: {