elanous 0.2.10 → 0.2.11

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 (584) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/Y4zKzL62sakG5momz3SoQ/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1394-556de6d71bbdb0fb.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
  7. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
  20. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
  25. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{8067-87ce253ec2f23697.js → 5845-e6c003ce8d5fd36a.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/8605-ea5bee9dfae512c7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-6a061bd97f0b9398.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-54b32bd27726588d.js → page-b12b6d2f8d2d1aea.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/chat/page-7c97dcdfe2e37a60.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/control/page-5721537ca8a3598c.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/design-check/page-b654240a572e4c48.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/editor/page-b9750abc897508c3.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/intake/page-c8b1ccda6ba7ae98.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-9688f5b4f1ff950f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-35414e0a007b4082.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e001823c35c9dd67.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a24072b35470157.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d37cb0d52f9b6bd5.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-686da0df9bf23e5c.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-239c17b911b84e72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-1218753c82d585f6.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/page-4b5935e67531efb5.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-3285bc87caf1eaa5.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-ad553b0e06505ecd.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → f48170cc-d05d28bf87a43bf4.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +1 -0
  105. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -0
  134. package/apps/pwa/out/inside/index.txt +31 -0
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/market/index.html +1 -1
  140. package/apps/pwa/out/market/index.txt +22 -22
  141. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  142. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  143. package/apps/pwa/out/morning/index.html +1 -1
  144. package/apps/pwa/out/morning/index.txt +23 -23
  145. package/apps/pwa/out/observatory/index.html +1 -1
  146. package/apps/pwa/out/observatory/index.txt +23 -23
  147. package/apps/pwa/out/ops/checklist/index.html +1 -1
  148. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  149. package/apps/pwa/out/ops/release/index.html +1 -1
  150. package/apps/pwa/out/ops/release/index.txt +22 -22
  151. package/apps/pwa/out/ops/seats/index.html +1 -1
  152. package/apps/pwa/out/ops/seats/index.txt +22 -22
  153. package/apps/pwa/out/outputs/index.html +1 -0
  154. package/apps/pwa/out/outputs/index.txt +30 -0
  155. package/apps/pwa/out/reflection/index.html +1 -1
  156. package/apps/pwa/out/reflection/index.txt +23 -23
  157. package/apps/pwa/out/scheduler/index.html +1 -1
  158. package/apps/pwa/out/scheduler/index.txt +23 -23
  159. package/apps/pwa/out/sessions/index.html +1 -1
  160. package/apps/pwa/out/sessions/index.txt +22 -22
  161. package/apps/pwa/out/settings/devices/index.html +1 -1
  162. package/apps/pwa/out/settings/devices/index.txt +23 -23
  163. package/apps/pwa/out/settings/index.html +1 -1
  164. package/apps/pwa/out/settings/index.txt +23 -23
  165. package/apps/pwa/out/setup/done/index.html +1 -1
  166. package/apps/pwa/out/setup/done/index.txt +23 -23
  167. package/apps/pwa/out/setup/index.html +1 -1
  168. package/apps/pwa/out/setup/index.txt +23 -23
  169. package/apps/pwa/out/share/index.html +1 -1
  170. package/apps/pwa/out/share/index.txt +23 -23
  171. package/apps/pwa/out/showroom/index.html +1 -1
  172. package/apps/pwa/out/showroom/index.txt +22 -22
  173. package/apps/pwa/out/tasks/index.html +1 -1
  174. package/apps/pwa/out/tasks/index.txt +23 -23
  175. package/apps/pwa/out/term/index.html +1 -1
  176. package/apps/pwa/out/term/index.txt +23 -24
  177. package/apps/pwa/out/today/index.html +1 -1
  178. package/apps/pwa/out/today/index.txt +23 -23
  179. package/apps/pwa/out/trace/index.html +1 -1
  180. package/apps/pwa/out/trace/index.txt +23 -23
  181. package/apps/pwa/out/vault/index.html +1 -1
  182. package/apps/pwa/out/vault/index.txt +23 -23
  183. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  184. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  185. package/apps/pwa/out/workflows/index.html +1 -1
  186. package/apps/pwa/out/workflows/index.txt +23 -23
  187. package/apps/pwa/out/workspace/index.html +1 -1
  188. package/apps/pwa/out/workspace/index.txt +23 -24
  189. package/apps/pwa/out/worktrees/index.html +1 -1
  190. package/apps/pwa/out/worktrees/index.txt +23 -23
  191. package/graphs/drafts/draft-cleanup.yaml +19 -0
  192. package/graphs/drafts/recipes.yaml +23 -0
  193. package/graphs/intake/intake-daily.yaml +2 -1
  194. package/graphs/ops/recipes.yaml +3 -0
  195. package/graphs/ops/secret-exposure.yaml +24 -0
  196. package/graphs/release/recipes.yaml +3 -0
  197. package/graphs/release/release-loop.yaml +4 -1
  198. package/graphs/video/explainer-line.yaml +2 -2
  199. package/package.json +2 -1
  200. package/plugins/elanous-hwp/README.md +25 -0
  201. package/plugins/elanous-hwp/cli.ts +22 -0
  202. package/plugins/elanous-hwp/converter.ts +237 -0
  203. package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
  204. package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
  205. package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
  206. package/plugins/elanous-hwp/plugin.json +16 -0
  207. package/plugins/elanous-hwp/plugin.ts +40 -0
  208. package/scripts/ci-public-export-leak-gate.ts +75 -14
  209. package/scripts/ci-public-leak-gate.ts +44 -9
  210. package/scripts/context-hooks/claude-stop.sh +4 -0
  211. package/scripts/context-hooks/emit.ts +24 -0
  212. package/scripts/context-hooks/post-tool-use.sh +4 -0
  213. package/scripts/coord-channel-watch.sh +12 -0
  214. package/scripts/coord-requests-bridge.ts +143 -0
  215. package/scripts/field-feed/lib.ts +4 -0
  216. package/scripts/fleet/node-setup.sh +20 -3
  217. package/scripts/homebrew-formula-bump.ts +69 -0
  218. package/scripts/marketing/measure-site-numbers.ts +169 -0
  219. package/scripts/measure-gate-shards.ts +309 -0
  220. package/scripts/mission-request-judge.ts +31 -1
  221. package/scripts/morning-report.ts +2 -2
  222. package/scripts/opportunity-scan.ts +1 -1
  223. package/scripts/release-loop/auto-approve-node.ts +21 -4
  224. package/scripts/release-loop/cut-branch.ts +84 -0
  225. package/scripts/release-loop/gate-node.ts +14 -13
  226. package/scripts/release-loop/mac-smoke-node.ts +229 -0
  227. package/scripts/release-loop/node-verdict.ts +3 -3
  228. package/scripts/release-loop/tui-node-checks.ts +42 -0
  229. package/scripts/release-loop/tui-sim-node.ts +32 -8
  230. package/scripts/release-loop/unattended-release.ts +5 -3
  231. package/scripts/release-loop/version-node.ts +61 -10
  232. package/scripts/signal-digest-cycle.ts +2 -7
  233. package/scripts/signal-digest-photo.ts +11 -0
  234. package/scripts/tech1-public-measure.ts +94 -0
  235. package/scripts/test-diet/node.ts +1 -1
  236. package/scripts/tui-regress.ts +2 -2
  237. package/scripts/ux/demo-rehearsal.ts +243 -0
  238. package/scripts/ux/lib/rehearsal-verdict.ts +73 -0
  239. package/skills/diagram-master/SKILL.md +366 -0
  240. package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
  241. package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
  242. package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
  243. package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
  244. package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
  245. package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
  246. package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
  247. package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
  248. package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
  249. package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
  250. package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
  251. package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
  252. package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
  253. package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
  254. package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
  255. package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
  256. package/skills/diagram-master/references/examples/README.md +69 -0
  257. package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
  258. package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
  259. package/skills/diagram-master/references/pyproject.toml +16 -0
  260. package/skills/diagram-master/references/quality-checklist.md +33 -0
  261. package/skills/diagram-master/references/routing-guide.md +164 -0
  262. package/skills/diagram-master/references/uv.lock +1356 -0
  263. package/skills/google-workspace/SKILL.md +33 -0
  264. package/skills/google-workspace/scripts/gws-safe.sh +177 -0
  265. package/skills/grill-me/SKILL.md +1 -1
  266. package/skills/lecture-note-digitizer/SKILL.md +926 -0
  267. package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
  268. package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
  269. package/skills/lecture-note-digitizer/references/html-template.md +167 -0
  270. package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
  271. package/skills/omni-crawl/SKILL.md +21 -11
  272. package/skills/omni-crawl/scripts/main.ts +6 -6
  273. package/skills/omni-crawl/src/router.ts +3 -3
  274. package/skills/omni-digest/SKILL.md +17 -9
  275. package/skills/omni-digest/digest.md +2 -2
  276. package/skills/omni-digest/src/fetch.ts +30 -3
  277. package/skills/pitch-deck/SKILL.md +2 -2
  278. package/skills/pitch-deck/template.html +2 -2
  279. package/skills/youtube-master/SKILL.md +163 -0
  280. package/skills/youtube-master/package-lock.json +558 -0
  281. package/skills/youtube-master/package.json +13 -0
  282. package/skills/youtube-master/references/study-note-template.md +124 -0
  283. package/skills/youtube-master/scripts/main.ts +435 -0
  284. package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
  285. package/skills/youtube-master/src/audio.ts +63 -0
  286. package/skills/youtube-master/src/domain-staging.ts +50 -0
  287. package/skills/youtube-master/src/env.ts +36 -0
  288. package/skills/youtube-master/src/obsidian.ts +155 -0
  289. package/skills/youtube-master/src/router.ts +110 -0
  290. package/skills/youtube-master/src/stt.ts +199 -0
  291. package/skills/youtube-master/src/study-note.ts +175 -0
  292. package/skills/youtube-master/src/summarize.ts +358 -0
  293. package/skills/youtube-master/src/transcript.ts +76 -0
  294. package/skills/youtube-master/src/types.ts +91 -0
  295. package/skills/youtube-master/src/util.ts +93 -0
  296. package/skills/youtube-master/src/youtube.ts +70 -0
  297. package/skills/youtube-master/tsconfig.json +13 -0
  298. package/src/acp/server.ts +5 -2
  299. package/src/agent-mission/driver.ts +80 -10
  300. package/src/agent-mission/pty-decision.ts +71 -0
  301. package/src/agent-mission/review-loop.ts +30 -0
  302. package/src/autopilot/build/llm-conflict-merge.ts +16 -3
  303. package/src/autopilot/tool-registry.ts +2 -0
  304. package/src/boot/daemon-prompt-turn.ts +5 -0
  305. package/src/boot/daemon-runtime.ts +9 -3
  306. package/src/boot/daemon-tools/web-search.ts +3 -0
  307. package/src/card-followup/core.ts +109 -0
  308. package/src/chat/index.ts +1 -0
  309. package/src/cli/drive-input-jail.ts +101 -0
  310. package/src/cli/hooks-cli.ts +17 -3
  311. package/src/cli/make-cli.ts +43 -0
  312. package/src/cli/persona-cli.ts +90 -4
  313. package/src/cli/plugin-cli.ts +31 -22
  314. package/src/cli/pod-cli.ts +19 -9
  315. package/src/cli/pr-cli.ts +51 -15
  316. package/src/cli/pty-drive-cli.ts +41 -7
  317. package/src/cli/pty-drive-verdict.ts +40 -0
  318. package/src/cli/release-cli.ts +17 -4
  319. package/src/cli/setup-cli.ts +43 -1
  320. package/src/cli/skills-cli.ts +17 -1
  321. package/src/context-bus/claude-hooks-install.ts +10 -0
  322. package/src/context-bus/context-now-surfaces.ts +48 -0
  323. package/src/context-bus/context-now.ts +104 -0
  324. package/src/context-bus/coord-events.ts +2 -2
  325. package/src/context-bus/external-events.ts +52 -0
  326. package/src/context-bus/long-term-memory.ts +108 -0
  327. package/src/dashboard/index.ts +9 -4
  328. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -1
  329. package/src/decisions/decision-cards.ts +10 -3
  330. package/src/decisions/decision-ledger.ts +13 -5
  331. package/src/discord/sprint21-runtime.ts +5 -1
  332. package/src/discord-self-message.ts +32 -0
  333. package/src/discord-slash-wire.ts +5 -1
  334. package/src/discord.ts +17 -1
  335. package/src/domains/core-tools.ts +3 -2
  336. package/src/domains/outbound-alert.ts +6 -1
  337. package/src/domains/telegram-kind-route.ts +51 -13
  338. package/src/exec-requests/default-outputs.ts +25 -0
  339. package/src/exec-requests/planner.ts +10 -2
  340. package/src/exec-requests/runner.ts +20 -6
  341. package/src/exec-requests/store.ts +3 -0
  342. package/src/execution-loop/launch-gate.ts +100 -0
  343. package/src/field/field-reel.ts +5 -1
  344. package/src/field-note/index.ts +201 -0
  345. package/src/field-note/transcribe.ts +248 -0
  346. package/src/graph-kinds/registry.ts +3 -3
  347. package/src/graph-runner/graph-cli.ts +61 -1
  348. package/src/graph-runner/graph-grow-llm.ts +37 -0
  349. package/src/graph-runner/graph-grow.ts +47 -0
  350. package/src/graph-runner/runner.ts +317 -25
  351. package/src/grok/agent-search.ts +8 -2
  352. package/src/guardian/secret-exposure-scan.ts +101 -0
  353. package/src/harness/harness-cli-command.ts +247 -7
  354. package/src/harness/harness-pod-dispatch.ts +43 -23
  355. package/src/harness/harness-seams.ts +38 -4
  356. package/src/harness/harness-stop.ts +23 -4
  357. package/src/harness/launch-registry.ts +91 -0
  358. package/src/harness/process-garbage.ts +5 -3
  359. package/src/hooks/dispatch.ts +82 -0
  360. package/src/hooks/expose.ts +254 -0
  361. package/src/hooks/github-shadow.ts +108 -0
  362. package/src/hooks/providers.ts +71 -5
  363. package/src/hooks/queue.ts +1 -1
  364. package/src/hooks/receiver.ts +21 -20
  365. package/src/index.ts +52 -14
  366. package/src/intake-plane/categorize.ts +18 -46
  367. package/src/intake-plane/discord-seat-work.ts +10 -0
  368. package/src/intake-plane/front-route-classifier.ts +33 -39
  369. package/src/intake-plane/persona-answer.ts +30 -0
  370. package/src/intake-plane/runtime-callables.ts +8 -1
  371. package/src/intake-plane/telegram-seat-work.ts +36 -2
  372. package/src/intake-plane/wish-folder.ts +67 -0
  373. package/src/intent-gate/gate.ts +30 -10
  374. package/src/live/detail-switch.ts +5 -0
  375. package/src/llm/difficulty.ts +57 -0
  376. package/src/llm/judge-layer.ts +101 -0
  377. package/src/llm/mission-router.ts +33 -19
  378. package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
  379. package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
  380. package/src/loops/contract/examples/steward.loop.yaml +50 -0
  381. package/src/loops/contract/loop-agent.schema.json +138 -0
  382. package/src/loops/contract/validate.ts +64 -0
  383. package/src/loops/registry.ts +20 -6
  384. package/src/market/signed-index.ts +4 -0
  385. package/src/maturity/bot-command-maturity.ts +45 -0
  386. package/src/maturity/feature-maturity.ts +26 -4
  387. package/src/model-tier/index.ts +2 -2
  388. package/src/model-tier/tier-resolver.ts +20 -20
  389. package/src/model-tier/types.ts +6 -6
  390. package/src/mss/logging/log-store.ts +15 -0
  391. package/src/nexus/api/config-model-tier.ts +5 -4
  392. package/src/nexus/api/fabric-plans.ts +53 -0
  393. package/src/nexus/api/harness-api.ts +5 -1
  394. package/src/nexus/api/health.ts +2 -0
  395. package/src/nexus/api/http-server.ts +36 -0
  396. package/src/nexus/api/inside-events.ts +226 -0
  397. package/src/nexus/api/meta-api.ts +21 -8
  398. package/src/nexus/api/missions.ts +6 -1
  399. package/src/nexus/api/ops-seats.ts +33 -0
  400. package/src/nexus/api/outputs-route.ts +44 -0
  401. package/src/nexus/api/seat-requests.ts +44 -2
  402. package/src/nexus/api/sessions-store.ts +17 -38
  403. package/src/nexus/api/sub-seat-charters.ts +34 -0
  404. package/src/nexus/api/tabs-mutations.ts +3 -1
  405. package/src/nexus/api/trace.ts +18 -6
  406. package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
  407. package/src/nexus/api/workflows.ts +80 -6
  408. package/src/nexus/index.ts +2 -0
  409. package/src/nexus/kinds/webterm.ts +2 -1
  410. package/src/nexus/webterm/pty.ts +6 -2
  411. package/src/onboarding/cli-auth-status.ts +80 -0
  412. package/src/onboarding/voice-ai.ts +3 -3
  413. package/src/org/seat-budget.ts +48 -0
  414. package/src/org/stall-escalation.ts +63 -0
  415. package/src/outputs/ledger.ts +120 -0
  416. package/src/persona/persona-todo.ts +79 -0
  417. package/src/persona/presets.ts +116 -2
  418. package/src/persona/team-set.ts +155 -0
  419. package/src/plugins/install/plugin-install.ts +14 -0
  420. package/src/plugins/maker/market-bundle.ts +109 -0
  421. package/src/plugins/maker/plugin-maker.ts +254 -11
  422. package/src/plugins/maker/wizard-generate.ts +103 -0
  423. package/src/pod-lease/admission.ts +150 -0
  424. package/src/pod-lease/dependency-state.ts +85 -0
  425. package/src/pod-lease/host-lease.ts +127 -0
  426. package/src/project/project-suggestion.ts +16 -0
  427. package/src/project/session-context.ts +24 -0
  428. package/src/release-loop/checklist.ts +2 -2
  429. package/src/release-loop/feature-store.ts +5 -3
  430. package/src/release-loop/next-md-merge.ts +58 -0
  431. package/src/release-loop/release-note.ts +59 -4
  432. package/src/seat-dispatch/seat-ask.ts +130 -0
  433. package/src/seat-dispatch/seat-questions.ts +100 -0
  434. package/src/seat-loop/seat-loop.ts +548 -29
  435. package/src/seat-loop/seat-report.ts +37 -2
  436. package/src/self-dev/dev-cli.ts +4 -1
  437. package/src/self-dev/dev-pipeline.ts +37 -1
  438. package/src/self-dev/draft-sweep.ts +38 -33
  439. package/src/self-dev/draft-triage-rules.ts +8 -5
  440. package/src/self-dev/fabric-plan-core.ts +297 -0
  441. package/src/self-dev/orchestrate.ts +36 -4
  442. package/src/self-dev/release-path-guard.ts +62 -0
  443. package/src/self-dev/shard-aggregate.ts +230 -0
  444. package/src/self-dev/shard-placement.ts +60 -0
  445. package/src/self-dev/shard-plan.ts +48 -0
  446. package/src/self-dev/shard-pod-run.ts +77 -0
  447. package/src/self-dev/shard-run.ts +105 -0
  448. package/src/self-implement/gate-baseline.ts +1 -1
  449. package/src/self-implement/gate-cli.ts +40 -1
  450. package/src/self-implement/gate-scope.ts +37 -7
  451. package/src/self-implement/graph-variant.ts +171 -0
  452. package/src/self-implement/harness-policy-gates.ts +1 -1
  453. package/src/self-implement/headless-elanous-driver.ts +22 -3
  454. package/src/self-implement/host-regate.ts +22 -1
  455. package/src/self-implement/importer-test-index.ts +2 -1
  456. package/src/self-implement/l8-merge-queue.ts +130 -0
  457. package/src/self-implement/orchestrator.ts +242 -9
  458. package/src/self-implement/route-consumer-test-index.ts +122 -0
  459. package/src/self-implement/seams.ts +72 -11
  460. package/src/self-implement/test-seams.ts +1 -0
  461. package/src/self-implement/uncommitted-work.ts +59 -0
  462. package/src/session/index.ts +36 -13
  463. package/src/skills/gift-install.ts +215 -0
  464. package/src/skills/tools/web-search.ts +4 -1
  465. package/src/steward/launch.ts +42 -26
  466. package/src/steward/steward-cards.ts +17 -9
  467. package/src/steward/triage.ts +46 -14
  468. package/src/task-cards/card-cli.ts +25 -1
  469. package/src/task-orchestrator/mission.ts +5 -0
  470. package/src/task-orchestrator/store.ts +12 -3
  471. package/src/task-orchestrator/surfaces/pod-command-job.ts +29 -7
  472. package/src/task-orchestrator/surfaces/pod-lease.ts +156 -19
  473. package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
  474. package/src/task-orchestrator/surfaces/self-implement-pod.ts +214 -40
  475. package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
  476. package/src/task-orchestrator/types.ts +2 -0
  477. package/src/telegram-card-followup.ts +16 -96
  478. package/src/telegram-commands.ts +10 -1
  479. package/src/telegram-project-command.ts +71 -0
  480. package/src/telegram-report.ts +21 -17
  481. package/src/telegram.ts +29 -3
  482. package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
  483. package/src/user-config.ts +102 -20
  484. package/src/version/packed-revision.json +1 -1
  485. package/src/web-search/grok.ts +7 -1
  486. package/src/web-search/index.ts +28 -1
  487. package/src/web-search/provider.ts +4 -0
  488. package/src/web-search/x-intent.ts +5 -0
  489. package/src/workflow-runtime/executor.ts +277 -47
  490. package/src/workflow-runtime/index.ts +1 -0
  491. package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
  492. package/src/workflow-runtime/plugin-kind-node.ts +1 -1
  493. package/src/workflow-runtime/schema.ts +79 -4
  494. package/src/workflow-runtime/types.ts +35 -4
  495. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  505. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  506. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/8195-6fde3d7a39859206.js +0 -10
  525. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/app/market/page-b31610deda76337b.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6cb0e1bbefe5fb3d.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e168fa1030af0548.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
  583. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +0 -1
  584. /package/apps/pwa/out/_next/static/{2DsiDfDCks44g1cPUMhiW → Y4zKzL62sakG5momz3SoQ}/_ssgManifest.js +0 -0
@@ -210,7 +210,7 @@ export function flushDeferred(path = DEFERRED_PATH): number {
210
210
  ].join('\n');
211
211
  observeFlush(items.length, lagMin, [...new Set(items.map(i => i.kind))]);
212
212
  let delivered = 0;
213
- // origin 없는 finance 는 기존 report 묶음. 운영 보류분은 별도로 묶어 운영 봇 경계를 유지한다.
213
+ // origin 없는 매매 알림만 report 묶음. 그 외는 kind 별로 운영 봇 경계를 유지한다.
214
214
  const noOrigin = items.filter(i => !i.origin);
215
215
  const tradingBatch = noOrigin.filter(i => !isOperationalKind(i.kind));
216
216
  if (tradingBatch.length && deliver(fmt(tradingBatch), 'report')) delivered += tradingBatch.length;
@@ -420,6 +420,11 @@ export function sendTelegramDirect(
420
420
  const target = kindRouteTarget(cfg, kind);
421
421
  if (target) return sendRaw(target.botToken, String(target.chatId), text);
422
422
  } catch { /* unavailable channel routing: fall through */ }
423
+ // A declared table is authoritative: missing roles cannot escape to an env bot.
424
+ if (cfg.telegram.channels?.length) {
425
+ logKindRouteFallback(kind, 'none', false);
426
+ return false;
427
+ }
423
428
  }
424
429
  if (isOperationalKind(kind)) {
425
430
  let home: ReturnType<typeof mainHomeTarget> = null;
@@ -1,22 +1,27 @@
1
1
  // 발송 목적(kind) → 텔레그램 채널 역할 · 대상. 가벼운 모듈(telegram.ts 를 import 하지 않는다 — outbound-alert 순환 방지).
2
- // 표에 없는 kind 는 undefined → 부르는 쪽이 폴백을 판정한다(투자 알림은 옛 reportChannel 유지).
2
+ // 명시적으로 매매 kind 인 경우만 trading; 나머지는 system. 역할 채널이 없으면 다른 역할로 폴백하지 않는다.
3
3
  import type { UserConfig } from '../user-config.js';
4
4
  import { resolveChannelBotToken } from '../channel-bot-token.js';
5
5
  import { debug } from '../debug/log.js';
6
- import { channelForRole, resolveTelegramChannels } from './telegram-channels.js';
6
+ import { resolveTelegramChannels } from './telegram-channels.js';
7
7
 
8
8
  export const DEFAULT_KIND_ROLES: Readonly<Record<string, string>> = {
9
+ report: 'report', alert: 'report', digest: 'report',
9
10
  intake: 'system', 'ops-report': 'system', 'ops-alert': 'system', 'ops-health': 'system',
10
- regression: 'system', 'agent-mission': 'system',
11
+ regression: 'system', 'agent-mission': 'system', brief: 'system', 'op-report': 'system',
11
12
  };
12
13
 
13
- /** No channel mapping for an operational kind may cross into a different bot. */
14
+ /** Only named finance delivery kinds can use the trading/report bot. */
15
+ export function isTradingKind(kind?: string): boolean {
16
+ return kind === 'report' || kind === 'alert' || kind === 'digest';
17
+ }
18
+
14
19
  export function isOperationalKind(kind?: string): boolean {
15
- return kind?.startsWith('ops-') ?? false;
20
+ return !isTradingKind(kind);
16
21
  }
17
22
 
18
23
  export function mainHomeTarget(cfg: UserConfig): { botToken: string; chatId: number } | null {
19
- const botToken = resolveChannelBotToken('telegram', cfg).token;
24
+ const botToken = operationsBotToken(cfg);
20
25
  const chatId = cfg.telegram.homeChannel ?? cfg.telegram.allowedUsers[0];
21
26
  return botToken && chatId != null && Number.isFinite(chatId) ? { botToken, chatId } : null;
22
27
  }
@@ -25,15 +30,48 @@ export function logKindRouteFallback(kind: string | undefined, to: 'report-chann
25
30
  try { debug.log('telegram.kind-route', 'fallback', { kind, to, sameBot }); } catch { /* logging must not prevent delivery */ }
26
31
  }
27
32
 
28
- export function roleForKind(cfg: UserConfig, kind?: string): string | undefined {
29
- if (!kind) return undefined;
30
- return cfg.telegram.kindRoles?.[kind] ?? DEFAULT_KIND_ROLES[kind];
33
+ function operationsBotToken(cfg: UserConfig): string | undefined {
34
+ const main = resolveChannelBotToken('telegram', cfg);
35
+ const channels = cfg.telegram.channels?.length ? resolveTelegramChannels(cfg.telegram) : [];
36
+ const tradingTokens = new Set(channels.filter(c => c.roles.includes('report')).map(c => c.botToken));
37
+ // The report (trading) bot is never the operations bot — with or without a channels table (review must-fix · OUT1).
38
+ if (cfg.telegram.reportChannel?.botToken) tradingTokens.add(cfg.telegram.reportChannel.botToken);
39
+ if (main.source !== 'channels.main') return main.token && !tradingTokens.has(main.token) ? main.token : undefined;
40
+ // channels.main may mean "first channel", which can be the trading bot.
41
+ // Only a named operations channel (or the named main channel) with an
42
+ // operational role establishes identity; array order and role alone do not.
43
+ const tokens = new Set(channels
44
+ .filter(c => (c.name === 'ops' && c.roles.includes('system'))
45
+ || (c.name === 'main' && c.roles.some(r => r === 'system' || r === 'qa' || r === 'default')))
46
+ .filter(c => !tradingTokens.has(c.botToken))
47
+ .map(c => c.botToken));
48
+ return tokens.size === 1 ? tokens.values().next().value : undefined;
49
+ }
50
+
51
+ function isAllowedBot(cfg: UserConfig, kind: string | undefined, token: string): boolean {
52
+ const mainToken = operationsBotToken(cfg);
53
+ if (!mainToken) return false;
54
+ if (!isTradingKind(kind)) return token === mainToken;
55
+ const reportToken = cfg.telegram.reportChannel?.botToken;
56
+ return token !== mainToken && (!reportToken || token === reportToken);
57
+ }
58
+
59
+ export function roleForKind(cfg: UserConfig, kind?: string): string {
60
+ const fallback = isTradingKind(kind) ? 'report' : 'system';
61
+ if (!kind || !cfg.telegram.channels?.length) return fallback;
62
+ const override = cfg.telegram.kindRoles?.[kind];
63
+ if (!override) return fallback;
64
+ const channel = resolveTelegramChannels(cfg.telegram)
65
+ .find(c => c.roles.includes(override) && isAllowedBot(cfg, kind, c.botToken));
66
+ return channel ? override : fallback;
31
67
  }
32
68
 
33
- /** The channel for a purpose kind — only with explicit `telegram.channels` and a mapped role. */
69
+ /** Exact role only: channelForRole's default/first fallback can cross bot boundaries. */
34
70
  export function kindRouteTarget(cfg: UserConfig, kind?: string): { botToken: string; chatId: number } | null {
71
+ if (!cfg.telegram.channels?.length) return null;
35
72
  const role = roleForKind(cfg, kind);
36
- if (!role || !cfg.telegram.channels?.length) return null;
37
- const ch = channelForRole(resolveTelegramChannels(cfg.telegram), role);
38
- return ch?.botToken && Number.isFinite(ch.chatId) ? { botToken: ch.botToken, chatId: ch.chatId } : null;
73
+ const ch = resolveTelegramChannels(cfg.telegram)
74
+ .find(c => c.roles.includes(role) && isAllowedBot(cfg, kind, c.botToken));
75
+ if (!ch || !Number.isFinite(ch.chatId)) return null;
76
+ return { botToken: ch.botToken, chatId: ch.chatId };
39
77
  }
@@ -0,0 +1,25 @@
1
+ export const OUTPUT_KINDS = {
2
+ report: { fileName: 'report.md', cardTitle: '한 장 보고서', guidance: '보고·정리·요약·회고 → 한 장 보고서', words: ['보고', '정리', '요약', '회고', 'report', 'summary', 'summarize', 'recap', 'brief'] },
3
+ slides: { fileName: 'slides.md', cardTitle: '슬라이드', guidance: '발표·자료·덱·피치 → 슬라이드(장마다 --- 구분)', words: ['발표', '자료', '덱', '피치', 'presentation', 'slides', 'slide', 'deck', 'pitch'] },
4
+ research: { fileName: 'research.md', cardTitle: '조사 문서', guidance: '조사·비교·경쟁사·시장 → 조사 문서(출처 링크 칸 필수)', words: ['조사', '비교', '경쟁사', '시장', 'research', 'compare', 'comparison', 'competitor', 'competitors', 'market'] },
5
+ post: { fileName: 'post.md', cardTitle: '게시글 초안', guidance: '게시글·캡션·공지·SNS → 게시글 초안(게시는 승인 노드 뒤)', words: ['게시글', '캡션', '공지', 'SNS', 'post', 'caption', 'announcement', 'social media'] },
6
+ video: { fileName: '', cardTitle: '영상', guidance: '영상·릴스·쇼츠 → 영상(그래프가 있을 때만)', words: ['영상', '릴스', '쇼츠', 'video', 'reels', 'shorts'] },
7
+ answer: { fileName: '', cardTitle: '답', guidance: '단순한 질문 → 답 그대로(산출물 없음)', words: [] },
8
+ } as const;
9
+
10
+ export type OutputKind = keyof typeof OUTPUT_KINDS;
11
+
12
+ export function pickDefaultOutput(text: string): { kind: OutputKind; fileName: string; cardTitle: string } {
13
+ const normalized = text.toLowerCase();
14
+ // Explicit artifact formats outrank activity words such as 정리, 요약 and 비교.
15
+ for (const kind of ['slides', 'post', 'video', 'research', 'report'] as const) {
16
+ const entry = OUTPUT_KINDS[kind];
17
+ if (entry.words.some(word => /[a-z]/i.test(word)
18
+ ? new RegExp(`(^|[^a-z])${word.toLowerCase()}(?=$|[^a-z])`).test(normalized)
19
+ : normalized.includes(word))) {
20
+ return { kind, fileName: entry.fileName, cardTitle: entry.cardTitle };
21
+ }
22
+ }
23
+ const kind = /(?:[??]|뭐|어떻게)\s*$/.test(text) ? 'answer' : 'report';
24
+ return { kind, fileName: OUTPUT_KINDS[kind].fileName, cardTitle: OUTPUT_KINDS[kind].cardTitle };
25
+ }
@@ -7,6 +7,7 @@ import { defaultGraphsDir } from '../self-implement/graph-templates.js';
7
7
  import { getElanousConfigDir } from '../elanous-config-dir.js';
8
8
  import type { ExecAttachment } from './store.js';
9
9
  import { debug } from '../debug/log.js';
10
+ import { OUTPUT_KINDS, pickDefaultOutput, type OutputKind } from './default-outputs.js';
10
11
 
11
12
  export interface InstalledGraph {
12
13
  id: string;
@@ -21,6 +22,8 @@ export interface ExecPlanItem {
21
22
  title: string;
22
23
  graphId: string;
23
24
  inputs: Record<string, unknown>;
25
+ /** Planner fills this for every returned row; older injected plan fixtures may omit it. */
26
+ output?: OutputKind;
24
27
  reason?: string;
25
28
  /** A5b — indexes of earlier items whose results this item needs (it starts after they finish). */
26
29
  after?: number[];
@@ -147,7 +150,7 @@ export function latestFieldFolder(configRoot = getElanousConfigDir()): string |
147
150
 
148
151
  export function execPlanPrompt(text: string, graphs: readonly InstalledGraph[], seats: readonly string[], attachments: readonly ExecAttachment[] = [], fieldFolder: string | null = latestFieldFolder()): string {
149
152
  const supplied = attachments.map(({ name, path }) => `첨부: ${name} (${/\.(?:png|jpe?g|webp|gif|heic)$/i.test(name) ? '이미지' : '파일'}) — ${path}`).join('\n');
150
- return `COO 역할. 요청을 실행 가능한 자리별 그래프로 나눠 맡겨라. JSON 배열만 출력: [{"seat":"자리","title":"할 일","graphId":"설치된 그래프 ID 또는 빈 문자열","inputs":{},"after":[]}]. 앞 항목의 결과가 있어야 할 수 있는 일(예: 점검·조사 «결과로» 쓰는 한 장 · 보고)은 after 에 그 앞 항목 번호(0부터)를 적는다 — 그 항목은 앞 항목이 끝난 뒤 그 결과를 받아 시작한다. 서로 기다릴 필요가 없으면 after 는 빈 배열. 자리 이름은 주어진 목록에서만, 그래프 ID는 설치된 목록에서만 고른다. 맞는 그래프가 없으면 graphId=""로 표시한다. 그래프에 inputKeys 가 있으면 inputs 를 그 키로만 채우되 한 줄과 아래 알려진 값에서 얻을 수 있는 값만 넣는다(모르는 값은 넣지 않는다). image/photo/file 입력에는 알맞은 첨부 경로를 쓸 수 있다. folder 입력에는 첨부가 없거나 첨부 사진이 그 현장 폴더 안에 있을 때만 오늘 현장 폴더를 쓸 수 있다. 다른 현장 사진이나 출처를 모르는 업로드 사진이 있으면 폴더를 추측하지 마라. 그 그래프가 꼭 받아야 할 값(예: 브랜드 이름 · 사진 파일)을 알 수 없으면 graphId=""로 두고 title 끝에 « — <무엇> 필요»를 적는다. 단순히 묻는 말에는 없는 그래프를 지어내지 말고 graphId=""로 표시한다. 없는 그래프나 산출을 지어내지 마라. 바깥 게시·발행·광고·결제는 승인 노드를 거치기 전 실행하면 안 된다.\n자리: ${JSON.stringify(seats)}\n실행 가능한 그래프: ${JSON.stringify(graphs.map(({ id, title, description, inputKeys }) => ({ id, title, description, ...(inputKeys ? { inputKeys } : {}) })))}\n${fieldFolder ? `알려진 값: 오늘 현장 폴더 = ${fieldFolder}\n` : ''}${supplied ? `${supplied}\n` : ''}한 줄: ${JSON.stringify(text)}`;
153
+ return `COO 역할. 요청을 실행 가능한 자리별 그래프로 나눠 맡겨라. JSON 배열만 출력: [{"seat":"자리","title":"할 일","graphId":"설치된 그래프 ID 또는 빈 문자열","inputs":{},"after":[]}]. 각 행에는 선택 칸 "output":"report|slides|research|post|video|answer"를 쓸 수 있다: ${Object.entries(OUTPUT_KINDS).map(([kind, entry]) => `${entry.guidance} = ${kind}${entry.fileName ? ` (${entry.fileName})` : ''}`).join(', ')}; 그 외 일은 report로 둔다. 앞 항목의 결과가 있어야 할 수 있는 일(예: 점검·조사 «결과로» 쓰는 한 장 · 보고)은 after 에 그 앞 항목 번호(0부터)를 적는다 — 그 항목은 앞 항목이 끝난 뒤 그 결과를 받아 시작한다. 서로 기다릴 필요가 없으면 after 는 빈 배열. 자리 이름은 주어진 목록에서만, 그래프 ID는 설치된 목록에서만 고른다. 맞는 그래프가 없으면 graphId=""로 표시한다. 그래프에 inputKeys 가 있으면 inputs 를 그 키로만 채우되 한 줄과 아래 알려진 값에서 얻을 수 있는 값만 넣는다(모르는 값은 넣지 않는다). image/photo/file 입력에는 알맞은 첨부 경로를 쓸 수 있다. folder 입력에는 첨부가 없거나 첨부 사진이 그 현장 폴더 안에 있을 때만 오늘 현장 폴더를 쓸 수 있다. 다른 현장 사진이나 출처를 모르는 업로드 사진이 있으면 폴더를 추측하지 마라. 그 그래프가 꼭 받아야 할 값(예: 브랜드 이름 · 사진 파일)을 알 수 없으면 graphId=""로 두고 title 끝에 « — <무엇> 필요»를 적는다. 단순히 묻는 말에는 없는 그래프를 지어내지 말고 graphId=""로 표시한다. 없는 그래프나 산출을 지어내지 마라. 바깥 게시·발행·광고·결제는 승인 노드를 거치기 전 실행하면 안 된다.\n자리: ${JSON.stringify(seats)}\n실행 가능한 그래프: ${JSON.stringify(graphs.map(({ id, title, description, inputKeys }) => ({ id, title, description, ...(inputKeys ? { inputKeys } : {}) })))}\n${fieldFolder ? `알려진 값: 오늘 현장 폴더 = ${fieldFolder}\n` : ''}${supplied ? `${supplied}\n` : ''}한 줄: ${JSON.stringify(text)}`;
151
154
  }
152
155
 
153
156
  export async function judgeExecPlan(text: string, graphs: readonly InstalledGraph[], seats: readonly string[], attachments: readonly ExecAttachment[] = [], fieldFolder: string | null = latestFieldFolder()): Promise<unknown> {
@@ -183,6 +186,11 @@ export async function planExecRequest(text: string, deps: {
183
186
  ? [...new Set(row.after.filter((n): n is number => Number.isInteger(n) && (n as number) >= 0 && (n as number) < index))]
184
187
  : [];
185
188
  const graph = known.get(row.graphId);
189
+ const modelOutput = typeof row.output === 'string' && Object.hasOwn(OUTPUT_KINDS, row.output)
190
+ ? row.output as OutputKind : undefined;
191
+ const picked = modelOutput ?? pickDefaultOutput(row.title).kind;
192
+ const output = picked === 'video' && !graph ? 'report' : picked;
193
+ debug.log('exec.plan', 'default-output', { kind: output, source: modelOutput === output ? 'model' : 'rule' });
186
194
  const inputs = { ...row.inputs as Record<string, unknown> };
187
195
  let folderReason: string | undefined;
188
196
  if (graph) {
@@ -206,7 +214,7 @@ export async function planExecRequest(text: string, deps: {
206
214
  }
207
215
  }
208
216
  return {
209
- seat: row.seat, title: row.title.trim(), graphId: row.graphId, inputs,
217
+ seat: row.seat, title: row.title.trim(), graphId: row.graphId, inputs, output,
210
218
  ...(after.length ? { after } : {}),
211
219
  ...(!graph ? { reason: `${row.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : folderReason ? { reason: folderReason } : {}),
212
220
  };
@@ -9,6 +9,12 @@ import { installedGraphs, planExecRequest, type ExecPlanItem, type InstalledGrap
9
9
  import { ExecRequestStore, type ExecAttachment, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
10
10
  import { answerAsSeat } from '../intake-plane/seat-answer.js';
11
11
  import { redactSecrets } from '../task-cards/card-store.js';
12
+ import { recordOutput } from '../outputs/ledger.js';
13
+ import type { OutputKind } from './default-outputs.js';
14
+
15
+ function recordResult(result: ExecResult, id: string, root: string, kind: OutputKind | 'file'): void {
16
+ recordOutput({ source: 'exec', sourceId: id, seat: result.seat, title: result.title, url: result.url, kind }, root);
17
+ }
12
18
 
13
19
  export interface ExecRunnerDeps {
14
20
  store?: ExecRequestStore;
@@ -137,14 +143,19 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
137
143
  for (const entry of entries) {
138
144
  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
139
145
  const result = resultOf(entry as Record<string, unknown>, seat, item.id, root);
140
- if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url)) item.results.push(result);
146
+ if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url)) {
147
+ item.results.push(result);
148
+ recordResult(result, item.id, root, seat.output ?? 'file');
149
+ }
141
150
  }
142
151
  }
143
152
  if (seat.status === 'done' && !item.results.some(existing => existing.seat === seat.seat)) {
144
153
  const file = textResultFile(seat, state, root);
145
154
  const result = file ? resultOf({ file, title: seat.title }, seat, item.id, root) : null;
146
- if (result) item.results.push(result);
147
- else {
155
+ if (result) {
156
+ item.results.push(result);
157
+ recordResult(result, item.id, root, seat.output ?? 'file');
158
+ } else {
148
159
  const keys = [...new Set((state.nodes ?? []).flatMap(node => Object.keys(lastJsonObject(node.output) ?? {})))];
149
160
  debug.log('exec.result', 'none', { seat: seat.seat, graphId: seat.graphId, keys });
150
161
  }
@@ -288,7 +299,7 @@ export class ExecRequestRunner {
288
299
  reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` });
289
300
  continue;
290
301
  }
291
- const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID() };
302
+ const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID(), ...(plan.output ? { output: plan.output } : {}) };
292
303
  const dir = join(this.root, 'graph-runs', seat.graphId);
293
304
  const statePath = join(dir, `${seat.runId}.json`);
294
305
  const artifactDir = join(dir, `${seat.runId}.json.artifacts`);
@@ -299,7 +310,10 @@ export class ExecRequestRunner {
299
310
  writeFileSync(statePath, JSON.stringify(state), { mode: 0o600 });
300
311
  item.seats.push(seat);
301
312
  const result = resultOf({ file, title: answered.title }, seat, id, this.root);
302
- if (result) item.results.push(result);
313
+ if (result) {
314
+ item.results.push(result);
315
+ recordResult(result, item.id, this.root, seat.output ?? 'file');
316
+ }
303
317
  }
304
318
  item.status = 'running';
305
319
  aggregate(item);
@@ -307,7 +321,7 @@ export class ExecRequestRunner {
307
321
  return;
308
322
  }
309
323
  item.seats = plans.map(plan => ({ seat: plan.seat, title: plan.title, status: plan.reason || !byId.has(plan.graphId) ? 'failed' : 'waiting', graphId: plan.graphId,
310
- runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.after?.length ? { after: plan.after } : {}),
324
+ runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.output ? { output: plan.output } : {}), ...(plan.after?.length ? { after: plan.after } : {}),
311
325
  ...(plan.reason || !byId.has(plan.graphId) ? { reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : {}) }));
312
326
  item.status = 'running';
313
327
  debug.log('exec-requests', 'planned', { id, seats: item.seats.map(seat => ({ seat: seat.seat, graphId: seat.graphId, status: seat.status })) });
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { getElanousConfigDir } from '../elanous-config-dir.js';
5
+ import type { OutputKind } from './default-outputs.js';
5
6
 
6
7
  export type SeatStatus = 'waiting' | 'running' | 'done' | 'failed';
7
8
  export interface ExecSeat {
@@ -10,6 +11,8 @@ export interface ExecSeat {
10
11
  status: SeatStatus;
11
12
  graphId: string;
12
13
  runId: string;
14
+ /** Requested output category, distinct from the result file MIME kind. */
15
+ output?: OutputKind;
13
16
  inputs?: Record<string, unknown>;
14
17
  reason?: string;
15
18
  /** A5b — indexes of earlier seats this seat waits for. */
@@ -0,0 +1,100 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { debug } from '../debug/log.js';
4
+ import { extractVerbatimOriginalAsk } from '../self-implement/goal-author.js';
5
+ import { loadRunLedger } from '../self-implement/run-ledger.js';
6
+ import { queryRunningRuns, type QueriedRunningRunsResult } from '../self-implement/running-runs.js';
7
+ import type { BudgetDecision } from '../self-implement/budget-gate.js';
8
+
9
+ export type ActiveRunsForGoal = readonly string[] | 'unknown';
10
+
11
+ /** Match an un-authored CLI request to the verbatim ask recorded in an authored run. */
12
+ export function launchRequestId(text: string): string {
13
+ return `request-${createHash('sha256').update(text).digest('hex').slice(0, 32)}`;
14
+ }
15
+
16
+ export interface ActiveRunsForGoalDeps {
17
+ queryRuns?: () => QueriedRunningRunsResult;
18
+ loadLedger?: typeof loadRunLedger;
19
+ readGoalDocument?: (path: string) => string;
20
+ }
21
+
22
+ /** Only a live process with an exact goal ID or verbatim authored ask is a duplicate. Incomplete observation is not absence. */
23
+ export function activeRunsForGoal(goalId: string, deps: ActiveRunsForGoalDeps = {}): ActiveRunsForGoal {
24
+ try {
25
+ const observation = (deps.queryRuns ?? (() => queryRunningRuns({ includeTest: true, caller: 'execution-loop.launch-gate', noCache: true })))();
26
+ const loadLedger = deps.loadLedger ?? loadRunLedger;
27
+ const readGoalDocument = deps.readGoalDocument ?? ((path: string) => readFileSync(path, 'utf8'));
28
+ let unknown = observation.completeness !== 'complete' || observation.pty.unreadable.length > 0;
29
+ const active = new Set<string>();
30
+ for (const run of observation.entries) {
31
+ if (run.status === 'ended-unclosed') continue;
32
+ if (run.ledgerDirectories.length === 0) { unknown = true; continue; }
33
+ let identified = false;
34
+ for (const directory of run.ledgerDirectories) {
35
+ try {
36
+ const ledger = loadLedger(run.runId, directory);
37
+ if (ledger === null) { unknown = true; continue; }
38
+ const ids = new Set(ledger.flatMap((entry) => entry.goalId ? [entry.goalId] : []));
39
+ if (ids.size !== 1) { unknown = true; continue; }
40
+ identified = true;
41
+ let matches = ids.has(goalId);
42
+ if (!matches && goalId.startsWith('request-') && run.status === 'running' && run.ptyRefs.length > 0) {
43
+ const document = ledger.find((entry) => entry.event === 'start')?.data.goalFile;
44
+ if (typeof document === 'string') {
45
+ try {
46
+ const ask = extractVerbatimOriginalAsk(readGoalDocument(document));
47
+ matches = ask?.range !== undefined && launchRequestId(ask.ask) === goalId;
48
+ } catch { unknown = true; }
49
+ }
50
+ }
51
+ if (matches) {
52
+ if (run.status === 'running' && run.ptyRefs.length > 0) active.add(run.runId);
53
+ else unknown = true;
54
+ }
55
+ } catch { unknown = true; }
56
+ }
57
+ if (!identified) unknown = true;
58
+ }
59
+ // Positive evidence wins even if a different process or store is unreadable.
60
+ return active.size > 0 ? [...active].sort() : unknown ? 'unknown' : [];
61
+ } catch {
62
+ return 'unknown';
63
+ }
64
+ }
65
+
66
+ export interface PreLaunchGateInput {
67
+ goalId: string;
68
+ budget?: BudgetDecision | 'unknown';
69
+ forceLaunch?: boolean;
70
+ }
71
+
72
+ export interface PreLaunchGateDeps extends ActiveRunsForGoalDeps {
73
+ activeRuns?: (goalId: string) => ActiveRunsForGoal;
74
+ }
75
+
76
+ export interface PreLaunchGateDecision {
77
+ action: 'proceed' | 'blocked-duplicate' | 'blocked-budget' | 'wait-reset';
78
+ sameGoalActiveRuns: ActiveRunsForGoal;
79
+ budget: BudgetDecision | 'unknown';
80
+ reason: string;
81
+ }
82
+
83
+ /** Force bypasses only a duplicate observation, never an exhausted budget. */
84
+ export function preLaunchGate(input: PreLaunchGateInput, deps: PreLaunchGateDeps = {}): PreLaunchGateDecision {
85
+ const decision = decidePreLaunch(input, deps);
86
+ debug.log('execution-loop.launch-gate', 'decision', { goalId: input.goalId, action: decision.action, sameGoalActiveRuns: decision.sameGoalActiveRuns, budget: decision.budget === 'unknown' ? 'unknown' : decision.budget.action, forceLaunch: input.forceLaunch === true, reason: decision.reason });
87
+ return decision;
88
+ }
89
+
90
+ function decidePreLaunch(input: PreLaunchGateInput, deps: PreLaunchGateDeps): PreLaunchGateDecision {
91
+ const sameGoalActiveRuns = (deps.activeRuns ?? ((goalId) => activeRunsForGoal(goalId, deps)))(input.goalId);
92
+ const budget = input.budget ?? 'unknown';
93
+ if (budget !== 'unknown' && (budget.action === 'stop' || budget.action === 'wait-reset')) {
94
+ return { action: budget.action === 'stop' ? 'blocked-budget' : 'wait-reset', sameGoalActiveRuns, budget, reason: budget.reasons.join(' · ') || `budget: ${budget.action}` };
95
+ }
96
+ if (sameGoalActiveRuns !== 'unknown' && sameGoalActiveRuns.length > 0 && !input.forceLaunch) {
97
+ return { action: 'blocked-duplicate', sameGoalActiveRuns, budget, reason: `same-goal active runs: ${sameGoalActiveRuns.join(', ')}` };
98
+ }
99
+ return { action: 'proceed', sameGoalActiveRuns, budget, reason: sameGoalActiveRuns === 'unknown' ? 'active runs unknown' : input.forceLaunch && sameGoalActiveRuns.length > 0 ? 'duplicate overridden by force-launch' : 'no confirmed duplicate' };
100
+ }
@@ -7,6 +7,7 @@ import { debug } from '../debug/log.js';
7
7
  import { mergeCapturedPath } from '../shell-env-bootstrap.js';
8
8
  import { listFieldMedia } from './field-media.js';
9
9
  import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
10
+ import { recordOutput } from '../outputs/ledger.js';
10
11
 
11
12
  export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
12
13
  export type FieldReelRunner = (dir: string, opts: { title: string; sub: string; mode?: 'standard' | 'instant' }) => Promise<FieldReelResult>;
@@ -137,6 +138,7 @@ async function render(dir: string, job: Job): Promise<void> {
137
138
  // 현재 렌더의 수신자 스냅샷. 렌더 중 업로드의 수신자는 다음 렌더로 넘긴다.
138
139
  const callbacks = job.callbacks;
139
140
  const renderOpts = job.opts;
141
+ let reelTitle = renderOpts.title || event;
140
142
  job.callbacks = new Map();
141
143
  writeStatus(dir, { state: 'rendering', items, startedAt });
142
144
  mkdirSync(join(dir, 'reel'), { recursive: true }); // 실행기 계약 — 산출 폴더는 있다(reel.sh 는 스스로 다시 만든다)
@@ -148,8 +150,9 @@ async function render(dir: string, job: Job): Promise<void> {
148
150
  let titleLines: string[] = [];
149
151
  try { titleLines = readFileSync(join(dir, 'title.txt'), 'utf8').split(/\r?\n/); }
150
152
  catch (err) { if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; }
153
+ reelTitle = titleLines[0]?.trim() || renderOpts.title || event;
151
154
  result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
152
- title: titleLines[0]?.trim() || renderOpts.title || event,
155
+ title: reelTitle,
153
156
  sub: titleLines[1]?.trim() || renderOpts.sub || sub,
154
157
  ...(renderOpts.mode ? { mode: renderOpts.mode } : {}),
155
158
  });
@@ -163,6 +166,7 @@ async function render(dir: string, job: Job): Promise<void> {
163
166
  writeStatus(dir, { state: result.ok ? 'done' : 'failed', items, startedAt, finishedAt,
164
167
  seconds: result.seconds, ...(result.ok ? { file: result.file } : { error: result.error ?? 'render failed' }) });
165
168
  debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
169
+ if (result.ok && result.file) recordOutput({ source: 'field-reel', sourceId: event, kind: 'video', title: reelTitle, path: result.file });
166
170
  if (result.ok) {
167
171
  // EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
168
172
  let cardsCache: 'hit' | 'miss' | 'n/a' = 'n/a';
@@ -0,0 +1,201 @@
1
+ export interface FieldPhoto {
2
+ file: string;
3
+ /** EXIF `YYYY:MM:DD HH:mm:ss`, ISO local time, or ISO time with an offset. */
4
+ exifTakenAt?: string | null;
5
+ }
6
+
7
+ export interface TranscriptEntry {
8
+ speaker: string;
9
+ timestamp: string;
10
+ text: string;
11
+ }
12
+
13
+ export interface FieldNoteOptions {
14
+ /** Minutes east of UTC for offset-free EXIF and transcript timestamps; KST by default. */
15
+ timeZoneOffsetMinutes?: number;
16
+ /** Minutes added to every dated photo after interpreting its EXIF timestamp. */
17
+ photoCorrectionMinutes?: number;
18
+ /** Maximum gap between consecutive photos in the same scene; 15 minutes by default. */
19
+ sceneGapMinutes?: number;
20
+ }
21
+
22
+ export interface TimedPhoto extends FieldPhoto {
23
+ takenAt: string;
24
+ }
25
+
26
+ export interface FieldScene {
27
+ start: string;
28
+ end: string;
29
+ photos: TimedPhoto[];
30
+ transcript: TranscriptEntry[];
31
+ }
32
+
33
+ export interface FieldTimeline {
34
+ scenes: FieldScene[];
35
+ undatedPhotos: FieldPhoto[];
36
+ /** Chronological entries when there are no dated photos to anchor a scene. */
37
+ transcriptWithoutPhotos: TranscriptEntry[];
38
+ timeZoneOffsetMinutes: number;
39
+ }
40
+
41
+ export interface FieldNoteLlm {
42
+ /** Only used for a raw transcript string; JSON entries bypass transcription. */
43
+ transcribe?: (source: string) => Promise<TranscriptEntry[]>;
44
+ summarize?: (entries: readonly TranscriptEntry[]) => Promise<string>;
45
+ }
46
+
47
+ const MINUTE = 60_000;
48
+
49
+ function validOffset(minutes: number): boolean {
50
+ return Number.isInteger(minutes) && minutes >= -14 * 60 && minutes <= 14 * 60;
51
+ }
52
+
53
+ function parseTime(value: string, offsetMinutes: number): number | null {
54
+ const input = value.trim();
55
+ const local = /^(\d{4})[:-](\d{2})[:-](\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/.exec(input);
56
+ const zoned = local ? null : /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(input);
57
+ const match = local ?? zoned;
58
+ if (!match) return null;
59
+ const [, year, month, day, hour, minute, second, fraction] = match;
60
+ if (+year! < 100) return null;
61
+ const milliseconds = fraction ? Number(fraction.padEnd(3, '0')) : 0;
62
+ const utc = Date.UTC(+year!, +month! - 1, +day!, +hour!, +minute!, +second!, milliseconds);
63
+ const date = new Date(utc);
64
+ if (date.getUTCFullYear() !== +year! || date.getUTCMonth() + 1 !== +month! ||
65
+ date.getUTCDate() !== +day! || date.getUTCHours() !== +hour! ||
66
+ date.getUTCMinutes() !== +minute! || date.getUTCSeconds() !== +second!) return null;
67
+ if (local) return utc - offsetMinutes * MINUTE;
68
+ // Offset-bearing ISO timestamps are absolute instants, not local wall-clock readings.
69
+ const parsed = Date.parse(input);
70
+ return Number.isFinite(parsed) ? parsed : null;
71
+ }
72
+
73
+ function iso(ms: number): string {
74
+ return new Date(ms).toISOString();
75
+ }
76
+
77
+ /** Pure alignment: neither mutates its inputs nor consults the machine clock/timezone. */
78
+ export function alignFieldNote(
79
+ photos: readonly FieldPhoto[],
80
+ transcript: readonly TranscriptEntry[],
81
+ options: FieldNoteOptions = {},
82
+ ): FieldTimeline {
83
+ const offset = options.timeZoneOffsetMinutes ?? 540;
84
+ const correction = options.photoCorrectionMinutes ?? 0;
85
+ const gap = options.sceneGapMinutes ?? 15;
86
+ if (!validOffset(offset) || !Number.isFinite(correction) || !Number.isFinite(gap) || gap < 0) {
87
+ throw new RangeError('Invalid field-note timezone, photo correction, or scene gap');
88
+ }
89
+ const dated: Array<{ photo: TimedPhoto; time: number; order: number }> = [];
90
+ const undatedPhotos: FieldPhoto[] = [];
91
+ photos.forEach((photo, order) => {
92
+ const parsed = photo.exifTakenAt ? parseTime(photo.exifTakenAt, offset) : null;
93
+ if (parsed === null) undatedPhotos.push({ ...photo });
94
+ else dated.push({ photo: { ...photo, takenAt: iso(parsed + correction * MINUTE) }, time: parsed + correction * MINUTE, order });
95
+ });
96
+ dated.sort((a, b) => a.time - b.time || a.order - b.order);
97
+
98
+ const groups: typeof dated[] = [];
99
+ for (const item of dated) {
100
+ const last = groups.at(-1);
101
+ if (!last || item.time - last[last.length - 1]!.time > gap * MINUTE) groups.push([item]);
102
+ else last.push(item);
103
+ }
104
+
105
+ const entries = transcript.map((entry, order) => {
106
+ const time = parseTime(entry.timestamp, offset);
107
+ if (time === null) throw new RangeError(`Invalid transcript timestamp at index ${order}: ${entry.timestamp}`);
108
+ return { entry: { ...entry }, time, order };
109
+ }).sort((a, b) => a.time - b.time || a.order - b.order);
110
+
111
+ // An utterance belongs to the scene whose photo span [first, last] is nearest in time (inside the span = distance 0);
112
+ // a tie goes to the earlier scene. So a 10:55 line next to an 11:00 photo joins the 11:00 scene, not 09:00.
113
+ const spans = groups.map((group) => ({ start: group[0]!.time, end: group[group.length - 1]!.time }));
114
+ const sceneEntries: Array<Array<(typeof entries)[number]>> = groups.map(() => []);
115
+ for (const item of entries) {
116
+ if (groups.length === 0) break;
117
+ let best = 0;
118
+ let bestDistance = Infinity;
119
+ spans.forEach((span, index) => {
120
+ const distance = item.time < span.start ? span.start - item.time : item.time > span.end ? item.time - span.end : 0;
121
+ if (distance < bestDistance) { best = index; bestDistance = distance; }
122
+ });
123
+ sceneEntries[best]!.push(item);
124
+ }
125
+ const scenes = groups.map((group, index) => {
126
+ const assigned = sceneEntries[index]!;
127
+ const times = [group[0]!.time, group[group.length - 1]!.time, ...assigned.map((item) => item.time)];
128
+ return {
129
+ start: iso(Math.min(...times)), end: iso(Math.max(...times)),
130
+ photos: group.map((item) => item.photo), transcript: assigned.map((item) => item.entry),
131
+ };
132
+ });
133
+ return {
134
+ scenes, undatedPhotos,
135
+ transcriptWithoutPhotos: groups.length === 0 ? entries.map((item) => item.entry) : [],
136
+ timeZoneOffsetMinutes: offset,
137
+ };
138
+ }
139
+
140
+ function localTime(instant: string, offset: number): string {
141
+ const shifted = new Date(Date.parse(instant) + offset * MINUTE).toISOString().slice(0, 16).replace('T', ' ');
142
+ const sign = offset >= 0 ? '+' : '-';
143
+ const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
144
+ const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
145
+ return `${shifted} UTC${sign}${hours}:${minutes}`;
146
+ }
147
+
148
+ function photoLine(photo: FieldPhoto, label: string): string {
149
+ const file = encodeURIComponent(photo.file).replace(/%2F/gi, '/').replace(/[()]/g, (char) => char === '(' ? '%28' : '%29');
150
+ const alt = photo.file.split(/[\\/]/).at(-1)?.replace(/\]/g, '\\]') ?? photo.file;
151
+ return `- ${label} ![${alt}](<${file}>)`;
152
+ }
153
+
154
+ /** Synchronous, side-effect-free Markdown rendering; summaries are optional placeholders. */
155
+ export function renderFieldNote(timeline: FieldTimeline, summaries: readonly string[] = []): string {
156
+ if (summaries.length > timeline.scenes.length + (timeline.transcriptWithoutPhotos.length ? 1 : 0)) {
157
+ throw new RangeError('More summaries than transcript sections');
158
+ }
159
+ const lines = ['# 현장 노트', ''];
160
+ timeline.scenes.forEach((scene, i) => {
161
+ lines.push(`## 장면 ${i + 1} · ${localTime(scene.start, timeline.timeZoneOffsetMinutes)} – ${localTime(scene.end, timeline.timeZoneOffsetMinutes)}`);
162
+ lines.push('', '### 전사 요약', summaries[i]?.trim() || '_(전사 요약 자리)_', '', '### 사진');
163
+ for (const photo of scene.photos) lines.push(photoLine(photo, localTime(photo.takenAt, timeline.timeZoneOffsetMinutes)));
164
+ lines.push('');
165
+ });
166
+ if (timeline.transcriptWithoutPhotos.length) {
167
+ lines.push('## 사진 없는 전사', '', '### 전사 요약',
168
+ summaries[timeline.scenes.length]?.trim() || '_(전사 요약 자리)_', '', '### 시간순 전사');
169
+ for (const entry of timeline.transcriptWithoutPhotos) {
170
+ lines.push(`- ${entry.timestamp} · ${entry.speaker}: ${entry.text}`);
171
+ }
172
+ lines.push('');
173
+ }
174
+ if (timeline.undatedPhotos.length) {
175
+ lines.push('## 시각 없음', '');
176
+ for (const photo of timeline.undatedPhotos) lines.push(photoLine(photo, '시각 없음'));
177
+ lines.push('');
178
+ }
179
+ return lines.join('\n').trimEnd() + '\n';
180
+ }
181
+
182
+ /** LLM calls are supplied by the caller; JSON transcripts need no transcription call. */
183
+ export async function generateFieldNote(
184
+ photos: readonly FieldPhoto[],
185
+ transcript: readonly TranscriptEntry[] | string,
186
+ llm: FieldNoteLlm = {},
187
+ options: FieldNoteOptions = {},
188
+ ): Promise<string> {
189
+ let entries: readonly TranscriptEntry[];
190
+ if (typeof transcript === 'string') {
191
+ if (!llm.transcribe) throw new Error('A transcribe dependency is required for raw transcript input');
192
+ entries = await llm.transcribe(transcript);
193
+ } else entries = transcript;
194
+ const timeline = alignFieldNote(photos, entries, options);
195
+ const sections = timeline.scenes.map((scene) => scene.transcript);
196
+ if (timeline.transcriptWithoutPhotos.length) sections.push(timeline.transcriptWithoutPhotos);
197
+ const summaries = llm.summarize
198
+ ? await Promise.all(sections.map((segment) => segment.length ? llm.summarize!(segment) : ''))
199
+ : [];
200
+ return renderFieldNote(timeline, summaries);
201
+ }