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
@@ -1,35 +1,71 @@
1
1
  import { execFile } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
3
4
  import { Database } from 'bun:sqlite';
4
5
  import { dirname, join, resolve } from 'node:path';
5
6
  import { promisify } from 'node:util';
6
7
  import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
7
8
  import { debug } from '../debug/log.js';
9
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
8
10
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
9
- import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
10
- import { listChecklist } from '../release-loop/checklist.js';
11
- import { readSchedules } from '../release-loop/feature-store.js';
11
+ import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
12
+ import { checkSeatBudget, type RunningSeatPod } from '../org/seat-budget.js';
13
+ import { DEFAULT_STALL_PARENTS, findStalls } from '../org/stall-escalation.js';
14
+ import { openMsgStore, type MessageEnvelope } from '../msg/msg-store.js';
15
+ import { loadLayeredPersonaDirs, resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
16
+ import { nextPersonaTodo, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
17
+ import { PersonaRegistry } from '../persona/registry.js';
18
+ import { answerSeat, askSeat, deliveredSeatQuestion, hasSeatAnswer, isSeatId, seatQuestions, type SeatId } from '../seat-dispatch/seat-questions.js';
19
+ import { queryRunningRuns } from '../self-implement/running-runs.js';
20
+ import { listChecklist, type Checklist } from '../release-loop/checklist.js';
21
+ import { readSchedules, releasedVersion } from '../release-loop/feature-store.js';
12
22
 
13
23
  // `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
14
- export type SeatItem = { source: 'request' | 'checklist'; kind?: 'request' | 'cell'; id: string; title: string; text: string; version?: string; queuedAt?: string; createdAt?: string };
24
+ export type SeatItem = { source: 'request' | 'checklist' | 'hook' | 'seat-question'; kind?: 'request' | 'cell' | 'hook-task' | 'seat-question'; id: string; title: string; text: string; evidence?: string; status?: string; version?: string; seat?: string; evidenceHash?: string; asOf?: string; queuedAt?: string; createdAt?: string; from?: SeatId };
15
25
  export type SeatInputs = { requests: SeatItem[]; checklist: SeatItem[]; role: string };
16
- // V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door or skipped-*) · `reason` (forbidden word for a decision).
17
- export type SeatAction = 'decision' | 'harness' | 'skipped-budget' | 'skipped-empty';
18
- export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; action?: SeatAction; reason?: string; runId?: string };
26
+ // V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door, wait or skipped-*) · `reason` (matched rule or forbidden word).
27
+ export type SeatAction = 'decision' | 'harness' | 'wait' | 'seat-question' | 'seat-answer' | 'skipped-budget' | 'skipped-empty';
28
+ export type SeatInquiry = { to: SeatId | 'CEO'; question: string };
29
+ export type SeatReply = { answer: string } | { to: 'CEO'; question: string };
30
+ export type OpCandidate =
31
+ | { kind: 'release-readiness'; version: string | null; verdict: 'ready' | 'not-ready' | 'no-open-release'; cutAt?: string; landBy?: string | null; red: string[]; undecided: string[]; blocked: string[] }
32
+ | { kind: 'unassigned-cell'; version: string | null; id: string | null; title?: string; status?: string; verdict: 'assign' | 'none' }
33
+ | { kind: 'decision-delegation'; id: string | null; title?: string; category?: DecisionEntry['category']; dueAt?: string; verdict: 'review-delegation' | 'none' };
34
+ export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate; action?: SeatAction; reason?: string; runId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean };
19
35
  export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
36
+ export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null };
20
37
  export type SeatDeps = {
38
+ personaConfig?: PersonaLoopConfig;
39
+ personaRegistry?: PersonaRegistry;
40
+ personaDir?: string;
41
+ appendPersona?: (path: string, entry: PersonaShadowEntry) => void;
21
42
  root?: string;
22
43
  repo?: string;
23
44
  config?: SeatLoopConfig;
45
+ budgetConfig?: Pick<UserConfig, 'org'>;
46
+ running?: () => readonly RunningSeatPod[];
47
+ escalate?: (parent: string, line: string) => Promise<void>;
24
48
  now?: () => Date;
25
49
  read?: (path: string) => string;
26
50
  run?: (args: string[]) => Promise<string>;
27
51
  append?: (path: string, entry: SeatEntry) => void;
28
52
  versions?: () => string[];
53
+ /** Release schedules (version · cutAt) — a version is open while its cut is still ahead. Default reads the release ledger; unreadable ⇒ no checklist candidates (fail closed). */
54
+ schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
29
55
  /** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
30
- checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null }>;
56
+ checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
57
+ /** Full checklist snapshot including history, for stall detection. */
58
+ stallChecklist?: (version: string) => Checklist;
59
+ /** Inject a mailbox write for isolated failure tests; production uses the seat message store. */
60
+ stallDelivery?: (message: MessageEnvelope, key: string, root: string) => boolean;
61
+ /** Open cards from the decision ledger; injected only for isolated ledger tests. */
62
+ pendingDecisions?: () => Array<Pick<DecisionEntry, 'id' | 'title' | 'category' | 'dueAt'>>;
31
63
  ledgerFiles?: (directory: string) => string[];
32
64
  lockContended?: () => void;
65
+ /** Supply a question only when this seat lacks evidence for the selected work item. */
66
+ inquire?: (seat: SeatId, item: SeatItem) => Promise<SeatInquiry | null> | SeatInquiry | null;
67
+ /** Supply the receiving seat's answer; absent evidence must not invent a reply. */
68
+ reply?: (seat: SeatId, question: string, from: SeatId) => Promise<SeatReply | null> | SeatReply | null;
33
69
  };
34
70
 
35
71
  const repoRoot = resolve(import.meta.dir, '../..');
@@ -84,6 +120,48 @@ export function seatLedgerPath(seat: string, root: string, now: Date): string {
84
120
  return join(root, 'seat-loop', seat, `${seatDay(now)}.jsonl`);
85
121
  }
86
122
 
123
+ export function personaShadowLedgerPath(personaId: string, root: string, now: Date): string {
124
+ // Use the todo module's ID guard for this second per-persona filesystem path too.
125
+ personaTodoPath(root, personaId);
126
+ return join(root, 'persona-loop', personaId, `${seatDay(now)}.jsonl`);
127
+ }
128
+
129
+ export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<PersonaShadowEntry[]> {
130
+ if ((deps.personaConfig ?? getUserConfig().loops?.persona)?.enabled !== true) return [];
131
+ const root = deps.root ?? effectiveInstanceRoot();
132
+ const dir = deps.personaDir ?? (deps.root ? join(root, 'personas') : resolveStatePersonaDir());
133
+ const registry = deps.personaRegistry ?? new PersonaRegistry();
134
+ if (!deps.personaRegistry) {
135
+ const loaded = await loadLayeredPersonaDirs(registry, deps.root ? [dir] : [dir, resolveRepositoryPersonaDir()]);
136
+ for (const error of loaded.errors) observe('persona-load-error', { error });
137
+ }
138
+ const now = (deps.now ?? (() => new Date()))();
139
+ const entries: PersonaShadowEntry[] = [];
140
+ for (const profile of registry.list().sort((a, b) => a.personaId.localeCompare(b.personaId))) {
141
+ const personaId = profile.personaId;
142
+ const { todos, errors } = readPersonaTodos(dir, personaId);
143
+ for (const error of errors) observe('persona-todo-error', { personaId, error });
144
+ const todo = nextPersonaTodo(todos);
145
+ const entry: PersonaShadowEntry = { personaId, ts: now.toISOString(), status: todo ? 'shadow' : 'skipped-empty', todo };
146
+ const path = personaShadowLedgerPath(personaId, root, now);
147
+ if (deps.appendPersona) deps.appendPersona(path, entry);
148
+ else {
149
+ mkdirSync(dirname(path), { recursive: true });
150
+ appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
151
+ }
152
+ observe('persona-shadow', { personaId, status: entry.status, todoId: todo?.id ?? null });
153
+ entries.push(entry);
154
+ }
155
+ return entries;
156
+ }
157
+
158
+ export async function runSeatLoopTurn(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
159
+ const result = await runSeatLoopOnce(seat, deps);
160
+ try { await runPersonaShadowOnce(deps); }
161
+ catch (error) { observe('persona-shadow-error', { error: String(error).slice(0, 200) }); }
162
+ return result;
163
+ }
164
+
87
165
  function rows(text: string): Record<string, unknown>[] {
88
166
  return text.split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
89
167
  }
@@ -93,17 +171,102 @@ function versionOrder(a: string, b: string): number {
93
171
  return (aa[0]! - bb[0]!) || (aa[1]! - bb[1]!) || (aa[2]! - bb[2]!);
94
172
  }
95
173
 
96
- export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promise<SeatInputs> {
174
+ function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
175
+
176
+ function itemSnapshot(item: SeatItem): string {
177
+ return JSON.stringify([item.title, item.text, item.evidence ?? null, item.status ?? null]);
178
+ }
179
+
180
+ function decisionRef(seat: string, item: SeatItem): string {
181
+ return `seat-loop:${seat}:${encodeURIComponent(itemKey(item))}`;
182
+ }
183
+
184
+ function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEntry | undefined {
185
+ const ref = decisionRef(seat, item);
186
+ return new DecisionLedger({ stateDir: root }).list({ status: 'all' }).find((card) => card.refs?.includes(ref));
187
+ }
188
+
189
+ // RM3 (asked/answered · decision-card receipts for seat inquiries) ⊕ main (checklist cells re-picked by evidence, not by a shadow row).
190
+ function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string): Set<string> {
191
+ const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
192
+ return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
193
+ || (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
194
+ || (entry.inquiry?.to === 'CEO' && !!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))
195
+ || (!inquiryRow(entry) && entry.status === 'outcome-unknown')
196
+ || (!inquiryRow(entry) && entry.status === 'attempting' && !(entry.item?.source === 'checklist' && ledger.slice(index + 1).some((later) =>
197
+ later.seat === entry.seat && later.status === 'launched' && later.item?.source === 'checklist'
198
+ && later.item.version === entry.item?.version && later.item.id === entry.item?.id
199
+ && later.item.title === entry.item?.title && later.item.evidenceHash === entry.item?.evidenceHash)))
200
+ || (shadow && entry.status === 'shadow' && entry.action !== 'wait' && entry.item?.source !== 'checklist')
201
+ || (entry.status === 'launched' && entry.item?.source !== 'checklist'))
202
+ .filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string')
203
+ .map((entry) => itemKey(entry.item)));
204
+ }
205
+
206
+ function eligibleItem(item: SeatItem, ledger: readonly SeatEntry[], handled: ReadonlySet<string>, shadow: boolean): boolean {
207
+ const key = itemKey(item);
208
+ if (handled.has(key)) return false;
209
+ const lastWait = [...ledger].reverse().find((entry) => entry.item && itemKey(entry.item) === key
210
+ && (entry.status === 'wait' || (shadow && entry.status === 'shadow' && entry.action === 'wait')));
211
+ return !lastWait?.item || itemSnapshot(lastWait.item) !== itemSnapshot(item);
212
+ }
213
+
214
+ // Compare checklist snapshots without consulting release history or treating a version move as new work.
215
+ export function alreadyHandled(item: SeatItem, ledger: readonly SeatEntry[]): boolean {
216
+ if (item.source !== 'checklist' || !item.seat || !item.asOf || item.evidenceHash === undefined) return false;
217
+ const now = Date.parse(item.asOf);
218
+ if (!Number.isFinite(now)) return false;
219
+ return ledger.some((entry) => {
220
+ const at = Date.parse(entry.at || entry.ts || '');
221
+ return entry.seat === item.seat && at >= now - 7 * 24 * 60 * 60 * 1000 && at <= now
222
+ // A shadowed seat question was never delivered — it must be picked again once delivery is on (RM3 · TC merge 10-03).
223
+ && (entry.status === 'launched' || (entry.status === 'shadow' && !entry.inquiry))
224
+ && entry.item?.source === 'checklist' && entry.item.id === item.id
225
+ && entry.item.title === item.title && entry.item.evidenceHash === item.evidenceHash;
226
+ });
227
+ }
228
+
229
+ function readSeatLedger(seat: string, deps: SeatDeps, now: Date): SeatEntry[] {
230
+ const directory = dirname(seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now));
231
+ const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
232
+ return files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
233
+ .flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
234
+ }
235
+
236
+ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger?: readonly SeatEntry[]): Promise<SeatInputs> {
97
237
  const read = deps.read ?? defaultRead;
98
238
  const root = deps.root ?? effectiveInstanceRoot();
239
+ const activeLedger = ledger ?? readSeatLedger(seat, deps, (deps.now ?? (() => new Date()))());
240
+ const consumed = handledKeys(activeLedger, (deps.config ?? getUserConfig().loops?.seat)?.mode === 'shadow', root);
99
241
  const requests = new Map<string, Record<string, unknown>>();
100
242
  for (const row of rows(read(join(root, 'seat-requests', 'requests.jsonl')))) {
101
243
  if (typeof row.key === 'string') requests.set(row.key, row);
102
244
  }
103
- const pending = [...requests.values()]
245
+ const pending: SeatItem[] = seatQuestions(root, seat as SeatId)
246
+ .filter((message) => !hasSeatAnswer(root, message) && !consumed.has(`seat-question::${message.id}`))
247
+ .map((message) => ({ source: 'seat-question', kind: 'seat-question', id: String(message.id), title: message.body, text: message.body,
248
+ from: message.from as SeatId, queuedAt: message.createdAt, createdAt: message.createdAt }));
249
+ const ordinary: SeatItem[] = [...requests.values()]
104
250
  .filter((row) => row.seat === seat && (row.status === 'pending' || row.status === 'queued') && typeof row.text === 'string')
105
- .map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
251
+ .map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), ...(typeof row.evidence === 'string' ? { evidence: row.evidence } : {}), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
106
252
  .sort((a, b) => a.queuedAt.localeCompare(b.queuedAt) || a.id.localeCompare(b.id));
253
+ const store = openMsgStore(join(root, 'msg', 'messages.db'));
254
+ const hookMessages: ReturnType<typeof store.listByRecipient> = [];
255
+ try {
256
+ let cursor = 0;
257
+ while (true) {
258
+ const batch = store.listByRecipient(seat, cursor, 1000);
259
+ hookMessages.push(...batch.filter(message => message.kind === 'hook-task'));
260
+ if (batch.length < 1000) break;
261
+ cursor = batch[batch.length - 1]!.id;
262
+ }
263
+ } finally { store.close(); }
264
+ for (const message of hookMessages) {
265
+ const item: SeatItem = { source: 'hook', kind: 'hook-task', id: String(message.id), title: message.body, text: message.body, queuedAt: message.createdAt, createdAt: message.createdAt };
266
+ if (eligibleItem(item, activeLedger, consumed, (deps.config ?? getUserConfig().loops?.seat)?.mode === 'shadow')) ordinary.push(item);
267
+ }
268
+ ordinary.sort((a, b) => (a.queuedAt ?? '').localeCompare(b.queuedAt ?? '') || a.id.localeCompare(b.id));
269
+ pending.push(...ordinary);
107
270
  const releaseRoot = deps.root ?? releaseLedgerRoot();
108
271
  const versions = deps.versions ?? (() => {
109
272
  const dir = join(releaseRoot, 'release');
@@ -113,16 +276,42 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promi
113
276
  return [...new Set([...fromDirs, ...fromSchedule])].filter((v) => /^\d+\.\d+\.\d+$/.test(v));
114
277
  });
115
278
  const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
279
+ let shipped: string | undefined;
280
+ try { shipped = releasedVersion(releaseRoot); }
281
+ catch (error) {
282
+ console.warn(`seat loop: cannot read published versions; retaining previous order (${String(error).slice(0, 200)})`);
283
+ }
284
+ // UX J2 criterion (10-03): only versions whose cut is still ahead are open; if the schedule can't be read, pick no cell.
285
+ const asOf = (deps.now ?? (() => new Date()))().toISOString();
286
+ const nowMs = Date.parse(asOf);
287
+ let open: Set<string> | null = null;
288
+ try {
289
+ const rows = (deps.schedules ?? (() => readSchedules()))();
290
+ open = new Set(rows.filter((row) => Number.isFinite(Date.parse(row.cutAt)) && Date.parse(row.cutAt) > nowMs).map((row) => row.version));
291
+ } catch (error) {
292
+ console.warn(`seat loop: cannot read release schedules; no checklist cell is picked (${String(error).slice(0, 200)})`);
293
+ debug.log('seat.loop', 'schedule-unreadable', { seat, error: String(error).slice(0, 200) });
294
+ }
116
295
  const checklist: SeatItem[] = [];
117
- for (const version of versions().sort(versionOrder)) {
296
+ for (const version of open === null ? [] : versions().sort(versionOrder)) {
297
+ if (!open!.has(version)) {
298
+ debug.log('seat.loop', 'skip-closed-version', { seat, version });
299
+ continue;
300
+ }
118
301
  let items: ReturnType<typeof itemsOf>;
119
302
  try { items = itemsOf(version); } catch (error) {
120
303
  debug.log('seat.loop', 'checklist-unreadable', { seat, version, error: String(error).slice(0, 200) });
121
304
  continue;
122
305
  }
306
+ if (shipped && versionOrder(version, shipped) <= 0) {
307
+ const cells = items.filter((item) => item.owner === seat && (item.status === 'yellow' || item.status === 'red')).length;
308
+ debug.log('seat.loop', 'skip-shipped-version', { version, cells });
309
+ continue;
310
+ }
123
311
  for (const item of items) {
124
312
  if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
125
- checklist.push({ source: 'checklist', kind: 'cell', version, id: item.id, title: item.title, text: item.title });
313
+ checklist.push({ source: 'checklist', kind: 'cell', version, seat, asOf, id: item.id, title: item.title, text: item.title, status: item.status,
314
+ ...(item.evidence ? { evidence: item.evidence } : {}), evidenceHash: createHash('sha256').update(item.evidence ?? '').digest('hex') });
126
315
  }
127
316
  }
128
317
  }
@@ -130,24 +319,139 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promi
130
319
  return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
131
320
  }
132
321
 
133
- function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
322
+ function opCandidates(deps: SeatDeps, now: Date): OpCandidate[] {
323
+ const releaseRoot = deps.root ?? releaseLedgerRoot();
324
+ const shipped = releasedVersion(releaseRoot);
325
+ let schedules: ReturnType<NonNullable<SeatDeps['schedules']>>;
326
+ try { schedules = (deps.schedules ?? (() => readSchedules(releaseRoot)))(); }
327
+ catch (error) {
328
+ observe('schedule-unreadable', { seat: 'OP', error: String(error).slice(0, 200) });
329
+ throw error;
330
+ }
331
+ const open = schedules.filter((row) => /^\d+\.\d+\.\d+$/.test(row.version)
332
+ && Number.isFinite(Date.parse(row.cutAt)) && Date.parse(row.cutAt) > now.getTime()
333
+ && (!shipped || versionOrder(row.version, shipped) > 0)).sort((a, b) => versionOrder(a.version, b.version));
334
+ const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
335
+ const releases = open.map((schedule) => ({ schedule, items: itemsOf(schedule.version).sort((a, b) => a.id.localeCompare(b.id)) }));
336
+ const first = releases[0];
337
+ const red = first?.items.filter((item) => item.status === 'red').map((item) => item.id) ?? [];
338
+ const undecided = first?.items.filter((item) => item.status === 'yellow' && !item.disposition).map((item) => item.id) ?? [];
339
+ const blocked = first?.items.filter((item) => item.status === 'yellow' && item.disposition === 'block').map((item) => item.id) ?? [];
340
+ const ready = first !== undefined && first.items.length > 0 && first.items.every((item) =>
341
+ item.status === 'green' || item.status === 'done'
342
+ || (item.status === 'yellow' && (item.disposition === 'move' || item.disposition === 'known-issue')));
343
+ const unassigned = releases.flatMap(({ schedule, items }) => items
344
+ .filter((item) => !item.owner && (item.status === 'yellow' || item.status === 'red'))
345
+ .map((item): OpCandidate => ({ kind: 'unassigned-cell', version: schedule.version, id: item.id,
346
+ title: item.title, status: item.status, verdict: 'assign' })));
347
+ const pending = (deps.pendingDecisions ?? (() => new DecisionLedger({ stateDir: deps.root }).list({ status: 'open' })))()
348
+ .sort((a, b) => (a.dueAt ?? '\uffff').localeCompare(b.dueAt ?? '\uffff') || a.id.localeCompare(b.id))[0];
349
+ return [
350
+ { kind: 'release-readiness', version: first?.schedule.version ?? null,
351
+ verdict: !first ? 'no-open-release' : ready ? 'ready' : 'not-ready',
352
+ ...(first ? { cutAt: first.schedule.cutAt, landBy: first.schedule.landBy ?? null } : {}), red, undecided, blocked },
353
+ ...(unassigned.length ? unassigned : [{ kind: 'unassigned-cell' as const, version: null, id: null, verdict: 'none' as const }]),
354
+ { kind: 'decision-delegation', id: pending?.id ?? null,
355
+ ...(pending ? { title: pending.title, category: pending.category, ...(pending.dueAt ? { dueAt: pending.dueAt } : {}) } : {}),
356
+ verdict: pending ? 'review-delegation' : 'none' },
357
+ ];
358
+ }
359
+
360
+ function deliverStall(message: MessageEnvelope, key: string, root: string): boolean {
361
+ const store = openMsgStore(join(root, 'msg', 'messages.db'));
362
+ try {
363
+ // Persist the exact stall identity alongside its message, atomically across processes.
364
+ store.db.exec('BEGIN IMMEDIATE');
365
+ try {
366
+ store.db.exec('CREATE TABLE IF NOT EXISTS seat_stall_deliveries (key TEXT PRIMARY KEY)');
367
+ const inserted = store.db.query('INSERT OR IGNORE INTO seat_stall_deliveries (key) VALUES (?)').run(key);
368
+ if (inserted.changes === 0) { store.db.exec('COMMIT'); return false; }
369
+ store.append(message);
370
+ store.db.exec('COMMIT');
371
+ return true;
372
+ } catch (error) { store.db.exec('ROLLBACK'); throw error; }
373
+ } finally { store.close(); }
374
+ }
375
+
376
+ function escalateStalls(seat: string, deps: SeatDeps, config: SeatLoopConfig, now: Date): void {
377
+ const root = deps.root ?? releaseLedgerRoot();
378
+ const schedules = (deps.schedules ?? (() => readSchedules(root)))();
379
+ const shipped = releasedVersion(root);
380
+ const failures: Error[] = [];
381
+ for (const schedule of schedules) {
382
+ if (!/^\d+\.\d+\.\d+$/.test(schedule.version)
383
+ || !Number.isFinite(Date.parse(schedule.cutAt)) || Date.parse(schedule.cutAt) <= now.getTime()
384
+ || (shipped && versionOrder(schedule.version, shipped) <= 0)) continue;
385
+ try {
386
+ // Same ledger root as the schedules and shipped version above (TC harvest · review round 3).
387
+ const checklist = (deps.stallChecklist ?? ((version: string) => listChecklist(version, deps.root)))(schedule.version);
388
+ for (const stall of findStalls(checklist, config, now)) {
389
+ if (stall.from !== seat) continue;
390
+ const { item, from, to, stalledMin, startedAt, reason } = stall;
391
+ const data = { item: item.id, from, to, stalledMin, version: schedule.version, reason };
392
+ if (config.mode === 'shadow') {
393
+ debug.log('org.stall', 'escalate', { ...data, mode: 'shadow' });
394
+ continue;
395
+ }
396
+ const key = JSON.stringify([schedule.version, item.id, from, to, reason, startedAt]);
397
+ const message: MessageEnvelope = { from, to, kind: 'stall-escalation',
398
+ body: `[stall:${key}] ${schedule.version} 체크리스트 ${item.id} · ${item.title} · ${reason} · ${stalledMin}분 정체 (${from} → ${to})` };
399
+ try {
400
+ const sent = (deps.stallDelivery ?? deliverStall)(message, key, deps.root ?? effectiveInstanceRoot());
401
+ if (sent) debug.log('org.stall', 'escalate', data);
402
+ } catch (error) {
403
+ failures.push(new Error(`stall escalation ${schedule.version}/${item.id}: ${String(error)}`, { cause: error }));
404
+ }
405
+ }
406
+ } catch (error) {
407
+ failures.push(new Error(`stall checklist ${schedule.version}: ${String(error)}`, { cause: error }));
408
+ }
409
+ }
410
+ if (failures.length) throw new AggregateError(failures, `stall escalation failed for ${seat}: ${failures.map((error) => error.message).join('; ')}`);
411
+ }
134
412
 
135
413
  // In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
136
- export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean } = {}): SeatItem | null {
137
- const handled = new Set(ledger.filter((entry) => entry.status === 'launched' || entry.status === 'hitl' || entry.status === 'attempting' || entry.status === 'outcome-unknown' || (opts.shadow === true && entry.status === 'shadow'))
138
- .filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string').map((entry) => itemKey(entry.item)));
139
- return [...inputs.requests, ...inputs.checklist].find((item) => !handled.has(itemKey(item))) ?? null;
414
+ export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string } = {}): SeatItem | null {
415
+ const handled = handledKeys(ledger, opts.shadow === true, opts.root);
416
+ return [...inputs.requests, ...inputs.checklist].find((item) => {
417
+ if (alreadyHandled(item, ledger)) {
418
+ debug.log('seat.loop', 'skip-handled', { id: item.id });
419
+ return false;
420
+ }
421
+ return eligibleItem(item, ledger, handled, opts.shadow === true);
422
+ }) ?? null;
140
423
  }
141
424
 
142
- export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness'; text: string; reason?: string } {
143
- const hit = `${item.id}\n${item.title}\n${item.text}`.match(TRACK_AGENT_FORBIDDEN_ACTION_REGEX);
144
- const kind = hit ? 'decision' : 'harness';
145
- return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(hit ? { reason: hit[0] } : {}) };
425
+ const SEAT_ACTION_RULES: ReadonlyArray<{ kind: 'wait' | 'harness' | 'decision'; signal: RegExp; source: 'content' | 'task'; resolvedBy?: RegExp }> = [
426
+ { kind: 'wait', signal: /실측\s*대기|(?:사람|실제)\s*기기(?!\s*(?:(?:실측|측정|확인)\s*)?완료)(?:\s*(?:실측|측정|확인|대기))?|실물\s*측정(?!\s*완료)/iu, source: 'content', resolvedBy: /(?:실측|측정|(?:사람|실제)\s*기기|실물\s*측정)\s*완료/iu },
427
+ { kind: 'wait', signal: /\u{1F451}\s*확인\s*대기/iu, source: 'content', resolvedBy: /\u{1F451}\s*확인\s*완료/iu },
428
+ { kind: 'decision', signal: TRACK_AGENT_FORBIDDEN_ACTION_REGEX, source: 'task' },
429
+ { kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: 'content' },
430
+ { kind: 'harness', signal: /(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록|운영\s*반영)(?:를?\s*위한)?\s*(?:준비|초안|원고|자료|구현|점검|테스트)|(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록)(?:용|를?\s*위한)\s*(?:초안|원고|자료|코드|기능)/iu, source: 'task' },
431
+ ];
432
+
433
+ export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness' | 'wait'; text: string; reason?: string } {
434
+ const task = `${item.id}\n${item.title}\n${item.text}`;
435
+ const content = `${task}\n${item.evidence ?? ''}`;
436
+ const preparation = SEAT_ACTION_RULES.find(({ kind }) => kind === 'harness')!;
437
+ const prepared = [...content.matchAll(new RegExp(preparation.signal.source, `${preparation.signal.flags.replace('g', '')}g`))];
438
+ const rule = SEAT_ACTION_RULES.map(({ kind, signal, source, resolvedBy }) => {
439
+ const text = source === 'content' ? content : task;
440
+ if (resolvedBy?.test(item.evidence ?? '')) return null;
441
+ const hits = [...text.matchAll(new RegExp(signal.source, `${signal.flags.replace('g', '')}g`))];
442
+ const hit = kind === 'decision' ? hits.find((match) => !prepared.some((prep) =>
443
+ match.index! >= prep.index! && match.index! + match[0].length <= prep.index! + prep[0].length)) : hits[0];
444
+ return hit ? { kind, reason: hit[0] } : null;
445
+ }).find((hit) => hit !== null);
446
+ const kind = rule?.kind ?? 'harness';
447
+ const reason = rule?.reason;
448
+ observe('plan', { kind, reason: reason ?? '' });
449
+ return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(reason ? { reason } : {}) };
146
450
  }
147
451
 
148
452
  // The launched sentence names the seat, the source and the release so same-named items stay distinct.
149
453
  export function seatTaskText(seat: string, item: SeatItem): string {
150
- const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : `자리 요청 ${item.id}`;
454
+ const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : item.source === 'hook' ? `웹훅 작업 카드 ${item.id}` : `자리 요청 ${item.id}`;
151
455
  return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
152
456
  }
153
457
 
@@ -162,21 +466,178 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
162
466
  const path = seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now);
163
467
  const directory = dirname(path);
164
468
  return withSeatLock(directory, async () => {
165
- const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
166
- const ledger = files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
167
- .flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
168
- const inputs = await gatherSeatInputs(seat, deps);
169
- const item = pickNext(inputs, ledger, { shadow: config.mode === 'shadow' });
469
+ const ledger = readSeatLedger(seat, deps, now);
470
+ const stateRoot = deps.root ?? effectiveInstanceRoot();
471
+ let turnError: unknown;
472
+ try {
473
+ if (seat === 'OP' && !seatQuestions(stateRoot, 'OP').some((question) =>
474
+ !hasSeatAnswer(stateRoot, question)
475
+ && !handledKeys(ledger, config.mode !== 'on' || (config.questions ?? 'shadow') !== 'on', stateRoot).has(`seat-question::${question.id}`))) {
476
+ const append = deps.append ?? defaultAppend;
477
+ const candidates = opCandidates(deps, now);
478
+ const assigned = candidates.filter((candidate): candidate is Extract<OpCandidate, { kind: 'unassigned-cell' }> => candidate.kind === 'unassigned-cell' && candidate.verdict === 'assign');
479
+ if (assigned.length) {
480
+ const previous = new Map<string, Extract<OpCandidate, { kind: 'unassigned-cell' }>>();
481
+ for (const row of ledger) {
482
+ const candidate = row.candidate;
483
+ if (candidate?.kind !== 'unassigned-cell') continue;
484
+ if (candidate.verdict === 'none' && !candidate.id) previous.clear();
485
+ else if (candidate.id) previous.set(JSON.stringify([candidate.version, candidate.id]), candidate);
486
+ }
487
+ const active = new Set(assigned.map((candidate) => JSON.stringify([candidate.version, candidate.id])));
488
+ for (const [key, candidate] of previous) {
489
+ if (candidate.verdict === 'assign' && !active.has(key)) candidates.push({ kind: 'unassigned-cell', version: candidate.version, id: candidate.id, verdict: 'none' });
490
+ }
491
+ }
492
+ const previousDecisions = new Set<string>();
493
+ for (const row of ledger) {
494
+ const candidate = row.candidate;
495
+ if (candidate?.kind !== 'decision-delegation') continue;
496
+ if (candidate.verdict === 'none' && !candidate.id) previousDecisions.clear();
497
+ else if (candidate.id) {
498
+ if (candidate.verdict === 'none') previousDecisions.delete(candidate.id);
499
+ else previousDecisions.add(candidate.id);
500
+ }
501
+ }
502
+ const currentDecision = candidates.find((candidate): candidate is Extract<OpCandidate, { kind: 'decision-delegation' }> =>
503
+ candidate.kind === 'decision-delegation' && candidate.verdict === 'review-delegation');
504
+ if (currentDecision) {
505
+ for (const id of previousDecisions) {
506
+ if (id !== currentDecision.id) candidates.push({ kind: 'decision-delegation', id, verdict: 'none' });
507
+ }
508
+ }
509
+ const entries = candidates.map((candidate): SeatEntry => ({ seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate }));
510
+ for (const entry of entries) {
511
+ const candidate = entry.candidate!;
512
+ const previous = [...ledger].reverse().find((row) => row.candidate?.kind === candidate.kind &&
513
+ (candidate.kind === 'release-readiness' || row.candidate?.kind === candidate.kind && (
514
+ candidate.kind === 'unassigned-cell' && row.candidate.kind === 'unassigned-cell'
515
+ ? (candidate.verdict === 'none' && !candidate.id && row.candidate.verdict === 'none' && !row.candidate.id)
516
+ || row.candidate.version === candidate.version && row.candidate.id === candidate.id
517
+ : candidate.kind === 'decision-delegation' && row.candidate.kind === 'decision-delegation'
518
+ && row.candidate.id === candidate.id)));
519
+ if (previous && JSON.stringify(previous.candidate) === JSON.stringify(candidate)) continue;
520
+ append(path, entry);
521
+ observe('op-judgment-shadow', { seat, candidate: entry.candidate, status: entry.status });
522
+ }
523
+ return entries[0]!;
524
+ }
525
+ const inputs = await gatherSeatInputs(seat, deps, ledger);
526
+ const questionsMode = config.mode === 'on' ? config.questions ?? 'shadow' : 'shadow';
527
+ const questionHandled = handledKeys(ledger, questionsMode !== 'on', stateRoot);
528
+ const pendingQuestion = inputs.requests.find((candidate) => candidate.source === 'seat-question' && !questionHandled.has(itemKey(candidate)));
529
+ const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger, { shadow: config.mode === 'shadow', root: stateRoot });
530
+ const item = pendingQuestion ?? ordinary;
531
+ const append = deps.append ?? defaultAppend;
532
+ // RM3 review must-fix: `questions` now has a default, so it can't be the trigger — a shadow seat loop never calls the model on its own.
533
+ if (item && (item.source === 'seat-question' || deps.inquire || (config.mode === 'on' && config.questions !== undefined))) {
534
+ const root = deps.root ?? effectiveInstanceRoot();
535
+ const unfinished = [...ledger].reverse().find((row) => row.item && itemKey(row.item) === itemKey(item)
536
+ && (row.status === 'attempting' || row.status === 'outcome-unknown') && (row.action === 'seat-question' || row.action === 'seat-answer'));
537
+ const inquiry = item.source === 'seat-question' ? null : unfinished?.inquiry ?? await (deps.inquire ?? (async (asking: SeatId, work: SeatItem) => { try {
538
+ const output = JSON.parse((await (deps.run ?? defaultRun)(['agent', '--json', '--no-tools',
539
+ `[${asking} 자리 · 역할 docs/roles/${asking}.md] 이 항목을 판단할 근거가 부족한 경우에만 다른 자리 또는 사람에게 질문한다. 근거가 충분하면 null. JSON 객체 하나만 반환: null 또는 {"to":"OP|TC|MK|UX|CEO","question":"필요한 근거를 묻는 질문"}. 항목: ${work.id} ${work.title}\n내용: ${work.text}\n역할: ${(deps.read ?? defaultRead)(join(deps.repo ?? repoRoot, 'docs', 'roles', `${asking}.md`)).slice(0, 4_000)}`])).trim()) as { reply?: unknown };
540
+ const value: unknown = typeof output?.reply === 'string' ? JSON.parse(output.reply) : output;
541
+ if (value === null) return null;
542
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid seat inquiry');
543
+ const record = value as Record<string, unknown>;
544
+ if ((record.to === 'CEO' || (typeof record.to === 'string' && isSeatId(record.to) && record.to !== asking))
545
+ && typeof record.question === 'string' && record.question.trim()) return { to: record.to as SeatId | 'CEO', question: record.question };
546
+ throw new Error('invalid seat inquiry');
547
+ } catch (error) {
548
+ // A failed judgment call must not break the record-only path — no question this turn.
549
+ observe('inquire-failed', { seat: asking, item: work.id, error: String(error).slice(0, 200) });
550
+ return null;
551
+ }
552
+ }))(seat as SeatId, item);
553
+ const reply = item.source === 'seat-question' && item.from && (questionsMode === 'on' || deps.reply || config.questions !== undefined)
554
+ ? unfinished?.answer ? { answer: unfinished.answer } : unfinished?.inquiry ? unfinished.inquiry : await (deps.reply ?? (async (receiver: SeatId, question: string, from: SeatId) => {
555
+ const evidence = inputs.checklist.map((candidate) =>
556
+ `${candidate.id} · ${candidate.title} (판 ${candidate.version}, ${candidate.kind})`).join('\n').slice(0, 8_000);
557
+ const output = JSON.parse((await (deps.run ?? defaultRun)(['agent', '--json', '--no-tools',
558
+ `[${receiver} 자리 · 역할 docs/roles/${receiver}.md] ${from} 자리의 질문에 근거를 갖고 답하라. 아래 ${receiver} 자리의 실제 체크리스트 근거에 없는 사실은 지어내지 마라. 모르면 사람 결정이 필요한 질문을 작성하라. JSON 하나만 반환: {"answer":"근거를 포함한 답"} 또는 {"to":"CEO","question":"결정할 질문"}. 질문: ${question}\n역할: ${inputs.role}\n${receiver} 체크리스트 근거:\n${evidence || '(없음)'}`])).trim()) as { reply?: unknown };
559
+ const value: unknown = typeof output.reply === 'string' ? JSON.parse(output.reply) : output;
560
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid seat reply');
561
+ const record = value as Record<string, unknown>;
562
+ if (typeof record.answer === 'string' && record.answer.trim()) return { answer: record.answer };
563
+ if (record.to === 'CEO' && typeof record.question === 'string' && record.question.trim()) return { to: 'CEO' as const, question: record.question };
564
+ throw new Error('invalid seat reply');
565
+ }))(seat as SeatId, item.text, item.from) : null;
566
+ if (item.source === 'seat-question' && reply && 'to' in reply && reply.to !== 'CEO') {
567
+ throw new Error('seat reply may only answer or escalate to CEO');
568
+ }
569
+ if (item.source === 'seat-question' || inquiry) {
570
+ const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), item, status: 'shadow',
571
+ action: item.source === 'seat-question' ? 'seat-answer' : inquiry?.to === 'CEO' ? 'decision' : 'seat-question',
572
+ ...(inquiry ? { inquiry } : {}), ...(reply && 'answer' in reply ? { answer: reply.answer } : {}) };
573
+ if (item.source === 'seat-question' && reply && 'to' in reply) {
574
+ entry.inquiry = reply;
575
+ entry.action = 'decision';
576
+ }
577
+ if (questionsMode !== 'on') {
578
+ append(path, entry);
579
+ observe('question-shadow', { seat, item, inquiry: entry.inquiry, answered: !!entry.answer });
580
+ return entry;
581
+ }
582
+ if (item.source === 'seat-question' && !reply) {
583
+ entry.status = 'awaiting-answer';
584
+ append(path, entry);
585
+ return entry;
586
+ }
587
+ append(path, { ...entry, status: 'attempting' });
588
+ try {
589
+ if (item.source === 'seat-question' && reply && 'answer' in reply) {
590
+ const question = { id: Number(item.id), from: item.from!, to: seat, body: item.text, kind: 'seat-question', createdAt: item.createdAt ?? '' };
591
+ if (!hasSeatAnswer(root, question)) answerSeat(root, question, reply.answer);
592
+ entry.status = 'answered';
593
+ } else {
594
+ const ask = entry.inquiry!;
595
+ if (!ask.question.trim()) throw new Error('empty seat question');
596
+ if (ask.to === 'CEO') {
597
+ const prior = decisionReceipt(root, seat, item);
598
+ if (!prior) {
599
+ const run = deps.run ?? defaultRun;
600
+ await run(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
601
+ '--s', `${seat} 질문 · ${item.title}`, '--c', '판단 근거 부족 — 사람의 결정이 필요하다',
602
+ '--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
603
+ '--skip-recommend', '근거가 부족하다', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
604
+ }
605
+ if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
606
+ entry.status = 'hitl';
607
+ } else {
608
+ if (!isSeatId(ask.to) || ask.to === seat) throw new Error('invalid recipient seat');
609
+ const key = `ask:${seat}:${itemKey(item)}`;
610
+ const delivered = deliveredSeatQuestion(root, key);
611
+ if (delivered && (delivered.from !== seat || delivered.to !== ask.to || delivered.body !== ask.question.trim())) {
612
+ throw new Error('seat delivery retry changed its recorded question');
613
+ }
614
+ askSeat(root, seat as SeatId, ask.to, ask.question, key);
615
+ entry.status = 'asked';
616
+ }
617
+ }
618
+ append(path, entry);
619
+ observe('question-outcome', { seat, item, status: entry.status });
620
+ return entry;
621
+ } catch (error) {
622
+ append(path, { ...entry, status: 'outcome-unknown' });
623
+ throw error;
624
+ }
625
+ }
626
+ }
170
627
  if (item) observe('picked', { seat, item, date: seatDay(now) });
171
628
  const planned = item ? planAction(item, seat) : undefined;
172
629
  const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
173
630
  ...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
174
- const append = deps.append ?? defaultAppend;
175
631
  if (config.mode === 'shadow' || !item) {
176
632
  append(path, entry);
177
633
  if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
178
634
  return entry;
179
635
  }
636
+ if (planned!.kind === 'wait') {
637
+ entry.status = 'wait';
638
+ append(path, entry);
639
+ return entry;
640
+ }
180
641
  const run = deps.run ?? defaultRun;
181
642
  let outcome: string | undefined;
182
643
  try { outcome = (JSON.parse(await run(['harness', 'budget', '--json'])) as { outcome?: string }).outcome; }
@@ -190,6 +651,52 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
190
651
  return entry;
191
652
  }
192
653
  const action = planned!;
654
+ if (action.kind === 'harness') {
655
+ const launchedRunIds = new Set(ledger.flatMap((row) => row.seat === seat && row.status === 'launched' && row.runId ? [row.runId] : []));
656
+ let running: readonly RunningSeatPod[] = [];
657
+ let observationUnavailable = false;
658
+ try {
659
+ if (deps.running) running = deps.running();
660
+ else if (launchedRunIds.size > 0) {
661
+ const observed = queryRunningRuns({ runIds: [...launchedRunIds], caller: 'seat-loop' });
662
+ if (observed.completeness !== 'complete' || observed.pty.unreadable.length > 0) observationUnavailable = true;
663
+ running = observed.entries
664
+ .filter((row) => launchedRunIds.has(row.runId) && (row.status === 'running' || row.status === 'probable-running' || row.status === 'unknown'))
665
+ // Seat launches always use `--substrate pod` (see the harness say call), so the substrate is known from the launch;
666
+ // only liveness can be uncertain — probable-running/unknown stay «unknown», never counted as confirmed Pods.
667
+ .map((row) => ({ seat, substrate: 'pod', status: row.status === 'running' ? 'running' : 'unknown' }));
668
+ }
669
+ } catch { observationUnavailable = true; }
670
+ const budget = observationUnavailable
671
+ ? { allowed: false as const, reason: `${seat} concurrent Pods budget cannot be verified (running observation unavailable)` }
672
+ : checkSeatBudget({ seat, now, ledger, running, ...(deps.budgetConfig ? { config: deps.budgetConfig } : {}) });
673
+ if (!budget.allowed) {
674
+ entry.status = 'skipped-budget';
675
+ entry.action = 'skipped-budget';
676
+ entry.reason = budget.reason;
677
+ observe('skipped-budget', { seat, item, reason: budget.reason });
678
+ const parent = (config.stall?.parents ?? {})[seat] ?? DEFAULT_STALL_PARENTS[seat];
679
+ // Dedupe only on a DELIVERED escalation, so a failed send is retried on the next turn (TC harvest · review round 1).
680
+ if (parent && parent !== seat && !ledger.some((prior) => prior.status === 'skipped-budget' && prior.escalated === true && prior.reason === budget.reason
681
+ && prior.item && itemKey(prior.item) === itemKey(item) && seatDay(new Date(prior.ts ?? prior.at)) === seatDay(now))) {
682
+ const line = `[${seat} → ${parent}] ${item.id}: ${budget.reason}`;
683
+ try {
684
+ if (deps.escalate) await deps.escalate(parent, line);
685
+ else {
686
+ const store = openMsgStore(join(deps.root ?? effectiveInstanceRoot(), 'msg', 'messages.db'));
687
+ try { store.append({ from: seat, to: parent, body: line, kind: 'seat-budget-escalation' }); }
688
+ finally { store.close(); }
689
+ }
690
+ entry.escalated = true;
691
+ } catch (error) {
692
+ entry.escalated = false;
693
+ observe('budget-escalation-failed', { seat, parent, item: item.id, error: String(error).slice(0, 200) });
694
+ }
695
+ }
696
+ append(path, entry);
697
+ return entry;
698
+ }
699
+ }
193
700
  // Persist the intent before crossing the process boundary: an unreturned call may already have acted.
194
701
  append(path, { ...entry, status: 'attempting' });
195
702
  try {
@@ -227,5 +734,17 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
227
734
  append(path, { ...entry, status: 'outcome-unknown' });
228
735
  throw error;
229
736
  }
737
+ } catch (error) {
738
+ turnError = error;
739
+ throw error;
740
+ } finally {
741
+ // A turn ends even when there is no picked item or a launch fails.
742
+ try { escalateStalls(seat, deps, config, (deps.now ?? (() => new Date()))()); }
743
+ catch (error) {
744
+ debug.log('org.stall', 'escalate-error', { seat, error: String(error).slice(0, 200) }, { level: 'warn' });
745
+ if (turnError !== undefined) throw new AggregateError([turnError, error], `seat turn failed (${String(turnError)}); stall escalation failed (${String(error)}) for ${seat}`);
746
+ throw error;
747
+ }
748
+ }
230
749
  }, deps.lockContended);
231
750
  }