elanous 0.2.10 → 0.2.12

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 (615) 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/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
  8. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
  18. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
  19. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
  24. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{8067-87ce253ec2f23697.js → 5845-e6c003ce8d5fd36a.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.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-cb169620945d1952.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-54b32bd27726588d.js → page-5c05b0da4bc1392c.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.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-1efd0cb9e0ed9d13.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.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-9547b5cec7cf76bf.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/page-e297866051595d81.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.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/128fb7fb670b2fbf.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/context/nightly-condense.yaml +21 -0
  192. package/graphs/context/recipes.yaml +12 -0
  193. package/graphs/demo/inside-seed.yaml +20 -0
  194. package/graphs/demo/recipes.yaml +12 -0
  195. package/graphs/drafts/draft-cleanup.yaml +19 -0
  196. package/graphs/drafts/recipes.yaml +23 -0
  197. package/graphs/intake/intake-daily.yaml +2 -1
  198. package/graphs/ops/recipes.yaml +3 -0
  199. package/graphs/ops/secret-exposure.yaml +24 -0
  200. package/graphs/release/recipes.yaml +3 -0
  201. package/graphs/release/release-loop.yaml +4 -1
  202. package/graphs/video/explainer-line.yaml +2 -2
  203. package/package.json +2 -1
  204. package/plugins/card-followup/examples/sample-card.png +0 -0
  205. package/plugins/elanous-hwp/README.md +25 -0
  206. package/plugins/elanous-hwp/cli.ts +22 -0
  207. package/plugins/elanous-hwp/converter.ts +237 -0
  208. package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
  209. package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
  210. package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
  211. package/plugins/elanous-hwp/plugin.json +16 -0
  212. package/plugins/elanous-hwp/plugin.ts +40 -0
  213. package/scripts/ci-public-export-leak-gate.ts +75 -14
  214. package/scripts/ci-public-leak-gate.ts +44 -9
  215. package/scripts/context-hooks/claude-stop.sh +4 -0
  216. package/scripts/context-hooks/emit.ts +24 -0
  217. package/scripts/context-hooks/post-tool-use.sh +4 -0
  218. package/scripts/coord-channel-watch.sh +12 -0
  219. package/scripts/coord-requests-bridge.ts +143 -0
  220. package/scripts/field-feed/lib.ts +4 -0
  221. package/scripts/fleet/node-setup.sh +20 -3
  222. package/scripts/homebrew-formula-bump.ts +69 -0
  223. package/scripts/lib/telegram-inject-args.ts +31 -0
  224. package/scripts/marketing/measure-site-numbers.ts +169 -0
  225. package/scripts/measure-gate-shards.ts +309 -0
  226. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  227. package/scripts/mission-request-judge.ts +31 -1
  228. package/scripts/morning-report.ts +2 -2
  229. package/scripts/opportunity-scan.ts +1 -1
  230. package/scripts/release-loop/auto-approve-node.ts +21 -4
  231. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  232. package/scripts/release-loop/cut-branch.ts +84 -0
  233. package/scripts/release-loop/gate-node.ts +22 -15
  234. package/scripts/release-loop/mac-smoke-node.ts +229 -0
  235. package/scripts/release-loop/node-verdict.ts +3 -3
  236. package/scripts/release-loop/ops-upgrade-node.ts +47 -6
  237. package/scripts/release-loop/publish-node.ts +4 -1
  238. package/scripts/release-loop/tui-node-checks.ts +42 -0
  239. package/scripts/release-loop/tui-sim-node.ts +32 -8
  240. package/scripts/release-loop/unattended-release.ts +5 -3
  241. package/scripts/release-loop/version-node.ts +74 -13
  242. package/scripts/signal-digest-cycle.ts +2 -7
  243. package/scripts/signal-digest-photo.ts +11 -0
  244. package/scripts/tech1-public-measure.ts +94 -0
  245. package/scripts/telegram-inject.ts +14 -6
  246. package/scripts/test-diet/node.ts +1 -1
  247. package/scripts/test-diet/remeasure-list.ts +112 -0
  248. package/scripts/tui-regress.ts +2 -2
  249. package/scripts/ux/demo-rehearsal.ts +247 -0
  250. package/scripts/ux/demo-seed.ts +108 -0
  251. package/scripts/ux/lib/rehearsal-verdict.ts +80 -0
  252. package/skills/diagram-master/SKILL.md +366 -0
  253. package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
  254. package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
  255. package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
  256. package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
  257. package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
  258. package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
  259. package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
  260. package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
  261. package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
  262. package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
  263. package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
  264. package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
  265. package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
  266. package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
  267. package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
  268. package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
  269. package/skills/diagram-master/references/examples/README.md +69 -0
  270. package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
  271. package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
  272. package/skills/diagram-master/references/pyproject.toml +16 -0
  273. package/skills/diagram-master/references/quality-checklist.md +33 -0
  274. package/skills/diagram-master/references/routing-guide.md +164 -0
  275. package/skills/diagram-master/references/uv.lock +1356 -0
  276. package/skills/google-workspace/SKILL.md +33 -0
  277. package/skills/google-workspace/scripts/gws-safe.sh +177 -0
  278. package/skills/grill-me/SKILL.md +1 -1
  279. package/skills/lecture-note-digitizer/SKILL.md +926 -0
  280. package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
  281. package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
  282. package/skills/lecture-note-digitizer/references/html-template.md +167 -0
  283. package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
  284. package/skills/omni-crawl/SKILL.md +21 -11
  285. package/skills/omni-crawl/scripts/main.ts +6 -6
  286. package/skills/omni-crawl/src/router.ts +3 -3
  287. package/skills/omni-digest/SKILL.md +17 -9
  288. package/skills/omni-digest/digest.md +2 -2
  289. package/skills/omni-digest/src/fetch.ts +30 -3
  290. package/skills/pitch-deck/SKILL.md +2 -2
  291. package/skills/pitch-deck/template.html +2 -2
  292. package/skills/youtube-master/SKILL.md +163 -0
  293. package/skills/youtube-master/package-lock.json +558 -0
  294. package/skills/youtube-master/package.json +13 -0
  295. package/skills/youtube-master/references/study-note-template.md +124 -0
  296. package/skills/youtube-master/scripts/main.ts +435 -0
  297. package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
  298. package/skills/youtube-master/src/audio.ts +63 -0
  299. package/skills/youtube-master/src/domain-staging.ts +50 -0
  300. package/skills/youtube-master/src/env.ts +36 -0
  301. package/skills/youtube-master/src/obsidian.ts +155 -0
  302. package/skills/youtube-master/src/router.ts +110 -0
  303. package/skills/youtube-master/src/stt.ts +199 -0
  304. package/skills/youtube-master/src/study-note.ts +175 -0
  305. package/skills/youtube-master/src/summarize.ts +358 -0
  306. package/skills/youtube-master/src/transcript.ts +76 -0
  307. package/skills/youtube-master/src/types.ts +91 -0
  308. package/skills/youtube-master/src/util.ts +93 -0
  309. package/skills/youtube-master/src/youtube.ts +70 -0
  310. package/skills/youtube-master/tsconfig.json +13 -0
  311. package/src/acp/server.ts +5 -2
  312. package/src/agent-mission/driver.ts +215 -23
  313. package/src/agent-mission/pty-decision.ts +71 -0
  314. package/src/agent-mission/review-loop.ts +30 -0
  315. package/src/autopilot/build/llm-conflict-merge.ts +16 -3
  316. package/src/autopilot/pty-control-loop.ts +4 -1
  317. package/src/autopilot/tool-registry.ts +2 -0
  318. package/src/boot/daemon-prompt-turn.ts +5 -0
  319. package/src/boot/daemon-runtime.ts +9 -3
  320. package/src/boot/daemon-tools/web-search.ts +3 -0
  321. package/src/card-followup/core.ts +109 -0
  322. package/src/chat/index.ts +2 -0
  323. package/src/cli/drive-input-jail.ts +101 -0
  324. package/src/cli/hooks-cli.ts +17 -3
  325. package/src/cli/make-cli.ts +43 -0
  326. package/src/cli/persona-cli.ts +98 -9
  327. package/src/cli/plugin-cli.ts +31 -22
  328. package/src/cli/pod-cli.ts +36 -9
  329. package/src/cli/pod-memory-advice.ts +35 -0
  330. package/src/cli/pr-cli.ts +51 -15
  331. package/src/cli/pty-drive-cli.ts +41 -7
  332. package/src/cli/pty-drive-verdict.ts +40 -0
  333. package/src/cli/release-cli.ts +63 -10
  334. package/src/cli/setup-cli.ts +43 -1
  335. package/src/cli/skills-cli.ts +17 -1
  336. package/src/context-bus/claude-hooks-install.ts +10 -0
  337. package/src/context-bus/context-now-surfaces.ts +65 -0
  338. package/src/context-bus/context-now.ts +104 -0
  339. package/src/context-bus/coord-events.ts +2 -2
  340. package/src/context-bus/external-events.ts +52 -0
  341. package/src/context-bus/long-term-memory.ts +108 -0
  342. package/src/context-bus/nightly-condense.ts +153 -0
  343. package/src/context-bus/session-events.ts +35 -0
  344. package/src/dashboard/dashboard-status-lines.ts +2 -0
  345. package/src/dashboard/index.ts +17 -4
  346. package/src/dashboard/slash-runtime/dashboard-handlers.ts +31 -1
  347. package/src/decisions/decision-cards.ts +10 -3
  348. package/src/decisions/decision-ledger.ts +13 -5
  349. package/src/discord/sprint21-runtime.ts +5 -1
  350. package/src/discord-self-message.ts +32 -0
  351. package/src/discord-slash-wire.ts +5 -1
  352. package/src/discord.ts +17 -1
  353. package/src/domains/core-tools.ts +3 -2
  354. package/src/domains/outbound-alert.ts +105 -32
  355. package/src/domains/outbound-log-probe.ts +8 -0
  356. package/src/domains/telegram-kind-route.ts +51 -13
  357. package/src/exec-requests/default-outputs.ts +25 -0
  358. package/src/exec-requests/planner.ts +10 -2
  359. package/src/exec-requests/runner.ts +20 -6
  360. package/src/exec-requests/store.ts +3 -0
  361. package/src/execution-loop/launch-gate.ts +109 -0
  362. package/src/field/field-reel.ts +5 -1
  363. package/src/field-note/index.ts +201 -0
  364. package/src/field-note/telegram.ts +142 -0
  365. package/src/field-note/transcribe.ts +248 -0
  366. package/src/graph-kinds/registry.ts +3 -3
  367. package/src/graph-runner/graph-cli.ts +61 -1
  368. package/src/graph-runner/graph-grow-llm.ts +47 -0
  369. package/src/graph-runner/graph-grow.ts +50 -0
  370. package/src/graph-runner/runner.ts +329 -33
  371. package/src/grok/agent-search.ts +8 -2
  372. package/src/guardian/secret-exposure-scan.ts +101 -0
  373. package/src/harness/harness-cli-command.ts +375 -13
  374. package/src/harness/harness-pod-dispatch.ts +43 -23
  375. package/src/harness/harness-seams.ts +38 -4
  376. package/src/harness/harness-stop.ts +23 -4
  377. package/src/harness/launch-registry.ts +91 -0
  378. package/src/harness/process-garbage.ts +5 -3
  379. package/src/hooks/consult-intake.ts +153 -0
  380. package/src/hooks/dispatch.ts +82 -0
  381. package/src/hooks/expose.ts +254 -0
  382. package/src/hooks/github-shadow.ts +108 -0
  383. package/src/hooks/providers.ts +71 -5
  384. package/src/hooks/queue.ts +1 -1
  385. package/src/hooks/receiver.ts +94 -16
  386. package/src/index.ts +76 -14
  387. package/src/intake-plane/categorize.ts +18 -46
  388. package/src/intake-plane/discord-seat-work.ts +38 -2
  389. package/src/intake-plane/front-route-classifier.ts +33 -39
  390. package/src/intake-plane/persona-answer.ts +30 -0
  391. package/src/intake-plane/runtime-callables.ts +8 -1
  392. package/src/intake-plane/telegram-seat-work.ts +36 -2
  393. package/src/intake-plane/wish-card.ts +33 -0
  394. package/src/intake-plane/wish-folder.ts +67 -0
  395. package/src/intent-gate/gate.ts +30 -10
  396. package/src/live/detail-switch.ts +5 -0
  397. package/src/llm/difficulty.ts +57 -0
  398. package/src/llm/judge-layer.ts +101 -0
  399. package/src/llm/mission-router.ts +33 -19
  400. package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
  401. package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
  402. package/src/loops/contract/examples/steward.loop.yaml +50 -0
  403. package/src/loops/contract/loop-agent.schema.json +138 -0
  404. package/src/loops/contract/validate.ts +64 -0
  405. package/src/loops/registry.ts +20 -6
  406. package/src/market/signed-index.ts +4 -0
  407. package/src/maturity/bot-command-maturity.ts +45 -0
  408. package/src/maturity/feature-maturity.ts +27 -4
  409. package/src/model-tier/index.ts +2 -2
  410. package/src/model-tier/tier-resolver.ts +20 -20
  411. package/src/model-tier/types.ts +6 -6
  412. package/src/mss/logging/log-store.ts +15 -0
  413. package/src/nexus/api/config-model-tier.ts +5 -4
  414. package/src/nexus/api/fabric-plans.ts +53 -0
  415. package/src/nexus/api/harness-api.ts +5 -1
  416. package/src/nexus/api/health.ts +2 -0
  417. package/src/nexus/api/http-server.ts +67 -8
  418. package/src/nexus/api/inside-events.ts +226 -0
  419. package/src/nexus/api/meta-api.ts +21 -8
  420. package/src/nexus/api/missions.ts +6 -1
  421. package/src/nexus/api/ops-seats.ts +33 -0
  422. package/src/nexus/api/outbound-report.ts +40 -7
  423. package/src/nexus/api/outputs-route.ts +44 -0
  424. package/src/nexus/api/personas.ts +95 -53
  425. package/src/nexus/api/seat-requests.ts +44 -2
  426. package/src/nexus/api/sessions-store.ts +17 -38
  427. package/src/nexus/api/sub-seat-charters.ts +34 -0
  428. package/src/nexus/api/tabs-mutations.ts +3 -1
  429. package/src/nexus/api/task-cards-api.ts +19 -0
  430. package/src/nexus/api/trace.ts +18 -6
  431. package/src/nexus/api/workflow-history.ts +88 -0
  432. package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
  433. package/src/nexus/api/workflows.ts +132 -6
  434. package/src/nexus/index.ts +2 -0
  435. package/src/nexus/kinds/webterm.ts +2 -1
  436. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  437. package/src/nexus/outbound/router.ts +8 -3
  438. package/src/nexus/webterm/pty.ts +6 -2
  439. package/src/onboarding/cli-auth-status.ts +80 -0
  440. package/src/onboarding/voice-ai.ts +3 -3
  441. package/src/org/seat-budget.ts +48 -0
  442. package/src/org/stall-escalation.ts +63 -0
  443. package/src/outputs/ledger.ts +120 -0
  444. package/src/persona/persona-todo.ts +79 -0
  445. package/src/persona/presets.ts +123 -2
  446. package/src/persona/team-set.ts +155 -0
  447. package/src/plugins/install/plugin-install.ts +14 -0
  448. package/src/plugins/maker/market-bundle.ts +110 -0
  449. package/src/plugins/maker/plugin-maker.ts +254 -11
  450. package/src/plugins/maker/wizard-generate.ts +103 -0
  451. package/src/pod-lease/admission.ts +150 -0
  452. package/src/pod-lease/dependency-state.ts +85 -0
  453. package/src/pod-lease/host-lease.ts +127 -0
  454. package/src/project/project-suggestion.ts +16 -0
  455. package/src/project/session-context.ts +24 -0
  456. package/src/release-loop/checklist.ts +17 -5
  457. package/src/release-loop/feature-store.ts +29 -13
  458. package/src/release-loop/landed-but-yellow.ts +48 -0
  459. package/src/release-loop/next-md-merge.ts +58 -0
  460. package/src/release-loop/release-note.ts +59 -4
  461. package/src/seat-dispatch/seat-ask.ts +130 -0
  462. package/src/seat-dispatch/seat-questions.ts +100 -0
  463. package/src/seat-loop/seat-loop.ts +709 -29
  464. package/src/seat-loop/seat-report.ts +37 -2
  465. package/src/self-dev/dev-cli.ts +4 -1
  466. package/src/self-dev/dev-pipeline.ts +37 -1
  467. package/src/self-dev/draft-sweep.ts +79 -40
  468. package/src/self-dev/draft-triage-rules.ts +62 -7
  469. package/src/self-dev/fabric-plan-core.ts +297 -0
  470. package/src/self-dev/orchestrate.ts +36 -4
  471. package/src/self-dev/release-path-guard.ts +62 -0
  472. package/src/self-dev/shard-aggregate.ts +230 -0
  473. package/src/self-dev/shard-placement.ts +60 -0
  474. package/src/self-dev/shard-plan.ts +48 -0
  475. package/src/self-dev/shard-pod-run.ts +77 -0
  476. package/src/self-dev/shard-run.ts +105 -0
  477. package/src/self-implement/budget-gate.ts +29 -12
  478. package/src/self-implement/gate-baseline.ts +1 -1
  479. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  480. package/src/self-implement/gate-cli.ts +40 -1
  481. package/src/self-implement/gate-scope.ts +44 -13
  482. package/src/self-implement/graph-variant.ts +171 -0
  483. package/src/self-implement/harness-policy-gates.ts +1 -1
  484. package/src/self-implement/headless-elanous-driver.ts +27 -3
  485. package/src/self-implement/host-regate.ts +22 -1
  486. package/src/self-implement/importer-test-index.ts +2 -1
  487. package/src/self-implement/l8-merge-queue.ts +130 -0
  488. package/src/self-implement/orchestrator.ts +257 -9
  489. package/src/self-implement/route-consumer-test-index.ts +122 -0
  490. package/src/self-implement/seams.ts +72 -11
  491. package/src/self-implement/test-seams.ts +2 -0
  492. package/src/self-implement/uncommitted-work.ts +59 -0
  493. package/src/session/index.ts +36 -13
  494. package/src/skills/gift-install.ts +215 -0
  495. package/src/skills/tools/web-search.ts +4 -1
  496. package/src/steward/launch.ts +42 -26
  497. package/src/steward/steward-cards.ts +17 -9
  498. package/src/steward/triage.ts +46 -14
  499. package/src/task-cards/card-cli.ts +25 -1
  500. package/src/task-orchestrator/mission.ts +5 -0
  501. package/src/task-orchestrator/store.ts +12 -3
  502. package/src/task-orchestrator/surfaces/pod-command-job.ts +43 -17
  503. package/src/task-orchestrator/surfaces/pod-lease.ts +156 -19
  504. package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
  505. package/src/task-orchestrator/surfaces/self-implement-pod.ts +222 -40
  506. package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
  507. package/src/task-orchestrator/types.ts +2 -0
  508. package/src/telegram-card-followup.ts +16 -96
  509. package/src/telegram-commands.ts +31 -1
  510. package/src/telegram-project-command.ts +145 -0
  511. package/src/telegram-report.ts +21 -17
  512. package/src/telegram.ts +61 -8
  513. package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
  514. package/src/user-config.ts +110 -24
  515. package/src/version/packed-revision.json +1 -1
  516. package/src/web-search/grok.ts +7 -1
  517. package/src/web-search/index.ts +28 -1
  518. package/src/web-search/provider.ts +4 -0
  519. package/src/web-search/x-intent.ts +5 -0
  520. package/src/workflow-runtime/executor.ts +277 -47
  521. package/src/workflow-runtime/index.ts +1 -0
  522. package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
  523. package/src/workflow-runtime/plugin-kind-node.ts +1 -1
  524. package/src/workflow-runtime/schema.ts +79 -4
  525. package/src/workflow-runtime/types.ts +35 -4
  526. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  536. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  537. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/8195-6fde3d7a39859206.js +0 -10
  556. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/market/page-b31610deda76337b.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6cb0e1bbefe5fb3d.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e168fa1030af0548.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
  614. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +0 -1
  615. /package/apps/pwa/out/_next/static/{2DsiDfDCks44g1cPUMhiW → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
@@ -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,109 @@
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 unknownReasons = new Set<string>();
30
+ if (observation.completeness !== 'complete') unknownReasons.add(`ledger observation ${observation.completeness}`);
31
+ for (const root of observation.pty.unreadable) unknownReasons.add(`unreadable PTY: ${root}`);
32
+ const active = new Set<string>();
33
+ for (const run of observation.entries) {
34
+ if (run.status === 'ended-unclosed') continue;
35
+ if (run.ledgerDirectories.length === 0) { unknown = true; unknownReasons.add(`${run.runId}: ledger directory absent`); continue; }
36
+ let identified = false;
37
+ for (const directory of run.ledgerDirectories) {
38
+ try {
39
+ const ledger = loadLedger(run.runId, directory);
40
+ if (ledger === null) { unknown = true; unknownReasons.add(`${run.runId}: ledger unavailable`); continue; }
41
+ const ids = new Set(ledger.flatMap((entry) => entry.goalId ? [entry.goalId] : []));
42
+ if (ids.size !== 1) { unknown = true; unknownReasons.add(`${run.runId}: expected one goalId, found ${ids.size}`); continue; }
43
+ identified = true;
44
+ let matches = ids.has(goalId);
45
+ if (!matches && goalId.startsWith('request-') && run.status === 'running' && run.ptyRefs.length > 0) {
46
+ const document = ledger.find((entry) => entry.event === 'start')?.data.goalFile;
47
+ if (typeof document === 'string') {
48
+ try {
49
+ const ask = extractVerbatimOriginalAsk(readGoalDocument(document));
50
+ matches = ask?.range !== undefined && launchRequestId(ask.ask) === goalId;
51
+ } catch { unknown = true; unknownReasons.add(`${run.runId}: goal document unreadable`); }
52
+ }
53
+ }
54
+ if (matches) {
55
+ if (run.status === 'running' && run.ptyRefs.length > 0) active.add(run.runId);
56
+ else { unknown = true; unknownReasons.add(`${run.runId}: matching ledger without live PTY`); }
57
+ }
58
+ } catch { unknown = true; unknownReasons.add(`${run.runId}: ledger read failed`); }
59
+ }
60
+ if (!identified) unknown = true;
61
+ }
62
+ // Positive evidence wins even if a different process or store is unreadable.
63
+ if (active.size > 0) return [...active].sort();
64
+ if (unknown) {
65
+ try { debug.log('execution-loop.launch-gate', 'active-runs-unknown', { goalId, reasons: [...unknownReasons] }); } catch { /* observation is fail-soft */ }
66
+ return 'unknown';
67
+ }
68
+ return [];
69
+ } catch (error) {
70
+ try { debug.log('execution-loop.launch-gate', 'active-runs-unknown', { goalId, reasons: [error instanceof Error ? error.message : String(error)] }); } catch { /* observation is fail-soft */ }
71
+ return 'unknown';
72
+ }
73
+ }
74
+
75
+ export interface PreLaunchGateInput {
76
+ goalId: string;
77
+ budget?: BudgetDecision | 'unknown';
78
+ forceLaunch?: boolean;
79
+ }
80
+
81
+ export interface PreLaunchGateDeps extends ActiveRunsForGoalDeps {
82
+ activeRuns?: (goalId: string) => ActiveRunsForGoal;
83
+ }
84
+
85
+ export interface PreLaunchGateDecision {
86
+ action: 'proceed' | 'blocked-duplicate' | 'blocked-budget' | 'wait-reset';
87
+ sameGoalActiveRuns: ActiveRunsForGoal;
88
+ budget: BudgetDecision | 'unknown';
89
+ reason: string;
90
+ }
91
+
92
+ /** Force bypasses only a duplicate observation, never an exhausted budget. */
93
+ export function preLaunchGate(input: PreLaunchGateInput, deps: PreLaunchGateDeps = {}): PreLaunchGateDecision {
94
+ const decision = decidePreLaunch(input, deps);
95
+ 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 });
96
+ return decision;
97
+ }
98
+
99
+ function decidePreLaunch(input: PreLaunchGateInput, deps: PreLaunchGateDeps): PreLaunchGateDecision {
100
+ const sameGoalActiveRuns = (deps.activeRuns ?? ((goalId) => activeRunsForGoal(goalId, deps)))(input.goalId);
101
+ const budget = input.budget ?? 'unknown';
102
+ if (budget !== 'unknown' && (budget.action === 'stop' || budget.action === 'wait-reset')) {
103
+ return { action: budget.action === 'stop' ? 'blocked-budget' : 'wait-reset', sameGoalActiveRuns, budget, reason: budget.reasons.join(' · ') || `budget: ${budget.action}` };
104
+ }
105
+ if (sameGoalActiveRuns !== 'unknown' && sameGoalActiveRuns.length > 0 && !input.forceLaunch) {
106
+ return { action: 'blocked-duplicate', sameGoalActiveRuns, budget, reason: `same-goal active runs: ${sameGoalActiveRuns.join(', ')}` };
107
+ }
108
+ return { action: 'proceed', sameGoalActiveRuns, budget, reason: sameGoalActiveRuns === 'unknown' ? 'active runs unknown' : input.forceLaunch && sameGoalActiveRuns.length > 0 ? 'duplicate overridden by force-launch' : 'no confirmed duplicate' };
109
+ }
@@ -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
+ }
@@ -0,0 +1,142 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { generateFieldNote, type FieldPhoto, type TranscriptEntry } from './index.js';
6
+ import { transcribeFieldAudio, type FieldAudio, type TranscriptionEngine } from './transcribe.js';
7
+ import type { TgIncoming, TgIncomingAttachment } from '../telegram.js';
8
+
9
+ interface SavedMedia { kind: 'photo' | 'voice' | 'audio'; file: string; receivedAt: string; messageId: number }
10
+ interface NoteWindow { title: string; start: string; end: string; status: 'open' | 'done'; openedBy: number; media: SavedMedia[]; notePath?: string }
11
+
12
+ export interface TelegramFieldNoteDeps {
13
+ rootDir: string;
14
+ download: (fileId: string) => Promise<{ localPath: string }>;
15
+ reply: (ctx: TgIncoming, text: string) => Promise<void>;
16
+ engine?: TranscriptionEngine;
17
+ decodeAudio?: (file: string, recordedAt: string) => FieldAudio;
18
+ now?: () => Date;
19
+ }
20
+
21
+ /** Decode Telegram's Ogg/Opus (or audio file) into NOTE3a's 16kHz mono float PCM. */
22
+ export function decodeFieldAudio(file: string, recordedAt: string): FieldAudio {
23
+ const result = spawnSync('ffmpeg', ['-v', 'error', '-i', file, '-f', 'f32le', '-ac', '1', '-ar', '16000', 'pipe:1'], { maxBuffer: 1024 * 1024 * 1024 });
24
+ if (result.status !== 0) throw new Error(`음성 디코딩 실패: ${result.stderr.toString().trim() || result.error?.message || 'ffmpeg'}`);
25
+ if (result.stdout.length % 4) throw new Error('음성 디코딩 실패: 잘못된 PCM 길이');
26
+ const bytes = result.stdout;
27
+ const samples = new Float32Array(bytes.length / 4);
28
+ for (let i = 0; i < samples.length; i++) samples[i] = bytes.readFloatLE(i * 4);
29
+ return { samples, sampleRate: 16000, recordedAt };
30
+ }
31
+
32
+ function windowPath(ctx: TgIncoming, root: string): string {
33
+ const bot = (ctx.botId ?? 'bot').replace(/[^a-zA-Z0-9_-]/g, '_');
34
+ return join(root, 'field-notes', 'telegram', bot, String(ctx.chatId), String(ctx.threadId ?? 0), String(ctx.userId), 'window.json');
35
+ }
36
+
37
+ function readWindow(path: string): NoteWindow | null {
38
+ return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) as NoteWindow : null;
39
+ }
40
+
41
+ function saveWindow(path: string, window: NoteWindow): void {
42
+ mkdirSync(join(path, '..'), { recursive: true });
43
+ const temp = `${path}.${randomUUID()}.tmp`;
44
+ try {
45
+ writeFileSync(temp, JSON.stringify(window, null, 2) + '\n');
46
+ renameSync(temp, path);
47
+ } finally {
48
+ if (existsSync(temp)) unlinkSync(temp);
49
+ }
50
+ }
51
+
52
+ function parseWindow(text: string, now: Date): { title: string; start: string; end: string } | null {
53
+ const match = /^노트:\s*(.+?)\s+(\d{2}):(\d{2})\s*[~~]\s*(\d{2}):(\d{2})$/.exec(text.trim());
54
+ if (!match) return null;
55
+ const [, title, h1, m1, h2, m2] = match;
56
+ if (!Number.isFinite(now.getTime()) || +h1! > 23 || +h2! > 23 || +m1! > 59 || +m2! > 59) return null;
57
+ const local = new Date(now.getTime() + 9 * 60 * 60_000).toISOString().slice(0, 10);
58
+ const start = new Date(`${local}T${h1}:${m1}:00+09:00`);
59
+ const end = new Date(`${local}T${h2}:${m2}:00+09:00`);
60
+ if (end <= start) return null;
61
+ return { title: title!.trim(), start: start.toISOString(), end: end.toISOString() };
62
+ }
63
+
64
+ export async function handleTelegramFieldNote(ctx: TgIncoming, deps: TelegramFieldNoteDeps): Promise<boolean> {
65
+ const text = ctx.text.trim();
66
+ const path = windowPath(ctx, deps.rootDir);
67
+ if (text.startsWith('노트:')) {
68
+ const parsed = parseWindow(text, ctx.receivedAt ? new Date(ctx.receivedAt) : (deps.now ?? (() => new Date()))());
69
+ if (!parsed) await deps.reply(ctx, '노트 시간 형식: 노트: 세미나 18:00~20:00');
70
+ else {
71
+ const previous = readWindow(path);
72
+ if (previous?.status === 'open' && previous.media.length) {
73
+ await deps.reply(ctx, '열린 노트 창이 있습니다 — 먼저 노트 만들기를 완료하세요.');
74
+ return true;
75
+ }
76
+ saveWindow(path, { ...parsed, status: 'open', openedBy: ctx.messageId, media: [] });
77
+ await deps.reply(ctx, `노트 창 열림 · ${parsed.title} · ${text.slice(text.lastIndexOf(' ') + 1)}`);
78
+ }
79
+ return true;
80
+ }
81
+ if (text !== '노트 만들기' && !ctx.attachments.some((a) => a.kind === 'photo' || a.kind === 'voice' || a.kind === 'audio')) return false;
82
+ const window = readWindow(path);
83
+ if (text === '노트 만들기') {
84
+ if (!window || window.status !== 'open') {
85
+ await deps.reply(ctx, '열린 노트 창 없음');
86
+ return true;
87
+ }
88
+ if (window.media.some((item) => item.kind !== 'photo') && !deps.engine) {
89
+ await deps.reply(ctx, '전사 엔진 없음 — NOTE3a 전사 엔진을 설정해 주세요. 모은 파일은 유지됩니다.');
90
+ return true;
91
+ }
92
+ try {
93
+ const photos: FieldPhoto[] = window.media.filter((item) => item.kind === 'photo').map((item) => ({ file: item.file, exifTakenAt: item.receivedAt }));
94
+ const transcript: TranscriptEntry[] = [];
95
+ for (const item of window.media.filter((m) => m.kind !== 'photo')) {
96
+ const audio = (deps.decodeAudio ?? decodeFieldAudio)(item.file, item.receivedAt);
97
+ transcript.push(...await transcribeFieldAudio(audio, deps.engine!));
98
+ }
99
+ const markdown = await generateFieldNote(photos, transcript, {
100
+ summarize: async (entries) => entries.map((entry) => `${entry.timestamp} · ${entry.speaker}: ${entry.text}`).join('\n'),
101
+ });
102
+ const notePath = join(path, '..', `note-${window.openedBy}.md`);
103
+ const noteTemp = `${notePath}.${randomUUID()}.tmp`;
104
+ try {
105
+ writeFileSync(noteTemp, `# ${window.title}\n\n시간 창: ${window.start} ~ ${window.end}\n\n${markdown}`);
106
+ renameSync(noteTemp, notePath);
107
+ } finally {
108
+ if (existsSync(noteTemp)) unlinkSync(noteTemp);
109
+ }
110
+ saveWindow(path, { ...window, status: 'done', notePath });
111
+ await deps.reply(ctx, `노트 생성 · ${notePath}`);
112
+ } catch (error) {
113
+ await deps.reply(ctx, `노트 생성 실패 · ${error instanceof Error ? error.message : String(error)}`);
114
+ }
115
+ return true;
116
+ }
117
+ if (!window || window.status !== 'open') return false;
118
+ const media = ctx.attachments.filter((a): a is TgIncomingAttachment & { kind: 'photo' | 'voice' | 'audio' } =>
119
+ a.kind === 'photo' || a.kind === 'voice' || a.kind === 'audio');
120
+ if (!media.length) return false;
121
+ const receivedAt = ctx.receivedAt ?? (deps.now ?? (() => new Date()))().toISOString();
122
+ if (ctx.messageId <= window.openedBy || receivedAt < window.start || receivedAt > window.end) return false;
123
+ const dir = join(path, '..', 'media');
124
+ mkdirSync(dir, { recursive: true });
125
+ try {
126
+ for (const [index, item] of media.entries()) {
127
+ if (window.media.some((saved) => saved.messageId === ctx.messageId && saved.kind === item.kind)) continue;
128
+ const downloaded = await deps.download(item.fileId);
129
+ const extension = item.kind === 'photo' ? 'jpg' : item.kind === 'voice' ? 'ogg'
130
+ : item.mimeType === 'audio/mpeg' ? 'mp3' : item.mimeType === 'audio/ogg' ? 'ogg' : 'audio';
131
+ const file = join(dir, `${ctx.messageId}-${index}.${extension}`);
132
+ try { copyFileSync(downloaded.localPath, file); }
133
+ finally { try { unlinkSync(downloaded.localPath); } catch { /* download may be reused by the caller */ } }
134
+ window.media.push({ kind: item.kind, file, receivedAt, messageId: ctx.messageId });
135
+ saveWindow(path, window);
136
+ }
137
+ await deps.reply(ctx, `노트 파일 모음 · 사진 ${window.media.filter((m) => m.kind === 'photo').length}장 · 음성 ${window.media.filter((m) => m.kind !== 'photo').length}개`);
138
+ } catch (error) {
139
+ await deps.reply(ctx, `노트 파일 저장 실패 · ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ return true;
142
+ }