elanous 0.2.13 → 0.2.14

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 (445) 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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +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 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  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/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/graphs/orchestrator/orchestrator.yaml +59 -0
  194. package/graphs/orchestrator/recipes.yaml +18 -0
  195. package/graphs/release/recipes.yaml +3 -0
  196. package/graphs/release/release-loop.yaml +4 -1
  197. package/graphs/rhythm/daily.yaml +38 -0
  198. package/graphs/rhythm/recipes.yaml +18 -0
  199. package/graphs/rhythm/weekly.yaml +39 -0
  200. package/graphs/video/recipes.yaml +3 -0
  201. package/graphs/video/youtube-summary.yaml +17 -0
  202. package/package.json +1 -1
  203. package/scripts/audit-test-state-writes.ts +72 -17
  204. package/scripts/brand/check.ts +186 -0
  205. package/scripts/coord-post.sh +11 -1
  206. package/scripts/docs-cli-check.ts +23 -6
  207. package/scripts/expose-rubric-check.ts +118 -0
  208. package/scripts/expose-rubric-suggest.ts +163 -0
  209. package/scripts/heal/run-role.ts +14 -0
  210. package/scripts/hq/standby-verify.ts +11 -7
  211. package/scripts/lessons/handbook-page.ts +85 -0
  212. package/scripts/release-loop/gate-node.ts +9 -0
  213. package/scripts/release-loop/publish-node.ts +14 -1
  214. package/scripts/release-loop/unattended-release.ts +23 -1
  215. package/scripts/release-loop/vault-note-node.ts +196 -0
  216. package/scripts/release-story/draft.ts +172 -0
  217. package/scripts/rhythm/daily.ts +383 -0
  218. package/scripts/rhythm/weekly.ts +294 -0
  219. package/scripts/skills/public-check.ts +169 -0
  220. package/scripts/test-deterministic.ts +4 -8
  221. package/scripts/test-diet/effectiveness.ts +195 -0
  222. package/scripts/test-diet/lib.ts +16 -8
  223. package/scripts/test-diet/node.ts +77 -9
  224. package/scripts/ux/demo-rehearsal.ts +2 -2
  225. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  226. package/scripts/video/youtube-summary.ts +133 -0
  227. package/skills/diagram-master/SKILL.md +42 -49
  228. package/skills/diagram-master/references/pyproject.toml +4 -1
  229. package/skills/diagram-master/references/routing-guide.md +4 -2
  230. package/skills/grill-me/SKILL.md +1 -0
  231. package/skills/youtube-master/SKILL.md +13 -13
  232. package/skills/youtube-master/package-lock.json +0 -545
  233. package/skills/youtube-master/package.json +2 -5
  234. package/skills/youtube-master/scripts/main.ts +2 -2
  235. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  236. package/skills/youtube-master/src/util.ts +30 -7
  237. package/src/agent-mission/driver.ts +3 -3
  238. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  239. package/src/autopilot/build/merge-intent.ts +53 -0
  240. package/src/chat/index.ts +2 -0
  241. package/src/claims/claims-ledger.ts +232 -0
  242. package/src/claims/claims-render.ts +157 -0
  243. package/src/cli/claims-cli.ts +94 -0
  244. package/src/cli/decisions-cli.ts +35 -3
  245. package/src/cli/docs-cli.ts +2 -0
  246. package/src/cli/flow-cli.ts +11 -0
  247. package/src/cli/freeze-cli.ts +45 -0
  248. package/src/cli/lesson-cli.ts +108 -0
  249. package/src/cli/logs-cli.ts +104 -2
  250. package/src/cli/logs-model-input.ts +131 -0
  251. package/src/cli/pr-cli.ts +29 -1
  252. package/src/cli/private-ledger-commands.ts +54 -4
  253. package/src/cli/pty-drive-verdict.ts +11 -2
  254. package/src/cli/release-cli.ts +144 -21
  255. package/src/cli/rfc-status.ts +91 -0
  256. package/src/connectors/linear.ts +5 -3
  257. package/src/context-bus/condense.ts +15 -20
  258. package/src/context-bus/context-now-public.ts +86 -0
  259. package/src/context-bus/context-now-surfaces.ts +55 -2
  260. package/src/context-bus/context-now.ts +4 -3
  261. package/src/context-bus/long-term-memory.ts +92 -11
  262. package/src/dashboard/index.ts +75 -22
  263. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  264. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  265. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  266. package/src/decisions/decision-ledger.ts +40 -1
  267. package/src/discord/sprint21-runtime.ts +16 -0
  268. package/src/discord-self-message.ts +8 -0
  269. package/src/discord-slash-wire.ts +31 -2
  270. package/src/domains/outbound-alert.ts +1 -1
  271. package/src/elanous-config-dir.ts +29 -4
  272. package/src/execution-loop/launch-gate.ts +29 -14
  273. package/src/flow/cards.ts +239 -0
  274. package/src/graph-runner/graph-tick.ts +4 -0
  275. package/src/graph-runner/runner.ts +30 -5
  276. package/src/harness/harness-cli-command.ts +152 -27
  277. package/src/harness/harness-incidents.ts +43 -12
  278. package/src/harness/harness-pod-dispatch.ts +2 -0
  279. package/src/harness/harness-queue-child.ts +3 -3
  280. package/src/harness/harness-queue.ts +216 -28
  281. package/src/harness/helper-scan.ts +139 -0
  282. package/src/harness/model-input-observation.ts +37 -0
  283. package/src/hq/hq.ts +1 -1
  284. package/src/hq/promote.ts +301 -0
  285. package/src/index.ts +83 -21
  286. package/src/intake-plane/linear-wish.ts +39 -11
  287. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  288. package/src/intake-plane/wish-card.ts +53 -2
  289. package/src/intake-plane/wish-folder.ts +5 -2
  290. package/src/intake-plane/wish-reply.ts +72 -0
  291. package/src/lessons/lesson-import.ts +106 -0
  292. package/src/lessons/lesson-ledger.ts +206 -0
  293. package/src/loops/activity.ts +203 -0
  294. package/src/loops/budget.ts +32 -0
  295. package/src/loops/checker.ts +5 -3
  296. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  297. package/src/loops/loop-cli.ts +36 -4
  298. package/src/loops/neighbors.ts +40 -0
  299. package/src/loops/observe.ts +10 -0
  300. package/src/loops/orchestrator/author-depth.ts +164 -0
  301. package/src/loops/orchestrator/author-input-check.ts +77 -0
  302. package/src/loops/orchestrator/author-ledger.ts +230 -0
  303. package/src/loops/orchestrator/finish-rate.ts +175 -0
  304. package/src/loops/orchestrator/overlap.ts +51 -12
  305. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  306. package/src/loops/orchestrator/tick.ts +302 -0
  307. package/src/loops/orchestrator/traffic.ts +18 -3
  308. package/src/loops/registry.ts +454 -9
  309. package/src/loops/status-rows.ts +118 -0
  310. package/src/loops/verdict.ts +17 -5
  311. package/src/maturity/bot-slash-catalog.ts +66 -0
  312. package/src/maturity/feature-maturity.ts +7 -4
  313. package/src/nexus/api/dashboard.ts +6 -1
  314. package/src/nexus/api/http-server.ts +18 -2
  315. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  316. package/src/nexus/api/schedules-read.ts +5 -1
  317. package/src/nexus/api/task-cards-api.ts +44 -5
  318. package/src/nexus/api/workflows.ts +2 -1
  319. package/src/release-loop/auto-start.ts +48 -0
  320. package/src/release-loop/checklist.ts +13 -4
  321. package/src/release-loop/feature-store.ts +28 -16
  322. package/src/release-loop/landing-freeze.ts +121 -0
  323. package/src/release-loop/placement.ts +242 -0
  324. package/src/release-loop/release-schedule.ts +3 -1
  325. package/src/seat-dispatch/ceo-commands.ts +2 -2
  326. package/src/seat-dispatch/seat-ask.ts +16 -3
  327. package/src/seat-dispatch/seat-questions.ts +58 -0
  328. package/src/seat-loop/seat-loop.ts +406 -31
  329. package/src/self-dev/dev-pipeline.ts +6 -2
  330. package/src/self-dev/draft-sweep.ts +76 -0
  331. package/src/self-dev/run-store.ts +73 -1
  332. package/src/self-dev/run-supervisor.ts +8 -0
  333. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  334. package/src/self-implement/budget-gate.ts +29 -0
  335. package/src/self-implement/expose-gate.ts +47 -0
  336. package/src/self-implement/frozen-merges.ts +294 -0
  337. package/src/self-implement/gate-cli.ts +10 -4
  338. package/src/self-implement/gate-scope.ts +46 -9
  339. package/src/self-implement/goal-archive.ts +222 -0
  340. package/src/self-implement/goal-lookup.ts +147 -0
  341. package/src/self-implement/heal-intake.ts +79 -0
  342. package/src/self-implement/host-regate.ts +25 -2
  343. package/src/self-implement/orchestrator.ts +114 -10
  344. package/src/self-implement/revert-guard.ts +62 -0
  345. package/src/self-implement/run-ledger.ts +1 -1
  346. package/src/self-implement/seams.ts +28 -8
  347. package/src/session/index.ts +2 -0
  348. package/src/task-cards/card-cli.ts +10 -2
  349. package/src/task-cards/card-store.ts +10 -6
  350. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  351. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  352. package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
  353. package/src/telegram-commands.ts +27 -3
  354. package/src/telegram-loops-command.ts +44 -0
  355. package/src/user-config.ts +120 -16
  356. package/src/version/packed-revision.json +1 -1
  357. package/src/workflow-runtime/storage.ts +31 -13
  358. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  369. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  373. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  444. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  445. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -2,14 +2,16 @@ import { execFileSync } from 'node:child_process';
2
2
  import { dlopen, FFIType } from 'bun:ffi';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
- import { constants as osConstants } from 'node:os';
6
- import { resolve, sep } from 'node:path';
5
+ import { constants as osConstants, hostname } from 'node:os';
6
+ import { basename, dirname, resolve, sep } from 'node:path';
7
7
  import { findGitDir } from '../git-fs/locate.js';
8
8
  import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
9
9
  import { loadSelfDevRun } from '../self-dev/run-store.js';
10
10
  import { normalizeRunId } from './harness-space.js';
11
- import { effectiveInstanceRoot } from '../instance/resolve.js';
12
- import { detectBursts, formatIncidentBurstWarning, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
11
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
12
+ import { lookupGoal, type GoalLookupOptions, type GoalLookupResult } from '../self-implement/goal-lookup.js';
13
+ import { archiveGoals, type GoalArchiveOptions } from '../self-implement/goal-archive.js';
14
+ import { detectBursts, formatIncidentBurstWarning, incidentLastLines, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
13
15
  import { Command, Option } from 'commander';
14
16
  import { runGitCommand } from '../git-fs/runner.js';
15
17
  import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, tracedPathReferences, type GoalType } from '../self-implement/goal-author.js';
@@ -29,7 +31,7 @@ import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
29
31
  import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
30
32
  import { getUserConfig } from '../user-config.js';
31
33
  import { installHarnessCliSinkHook } from './harness-cli-sink.js';
32
- import { addHarnessQueue, listHarnessQueue, reconcileHarnessQueue, removeHarnessQueue, tickHarnessQueue, type HarnessQueueDeps } from './harness-queue.js';
34
+ import { addHarnessQueue, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, tickHarnessQueue, type HarnessQueueDeps, type QueueSeat } from './harness-queue.js';
33
35
  import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from './harness-substrate-default.js';
34
36
  import { runHarnessPlanRfc } from './harness-plan-rfc.js';
35
37
  import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
@@ -92,6 +94,7 @@ export class HarnessCliInputError extends Error {
92
94
  }
93
95
 
94
96
  export interface HarnessAskSayOptions {
97
+ seat?: QueueSeat;
95
98
  json?: boolean;
96
99
  base?: string;
97
100
  target?: string;
@@ -253,6 +256,7 @@ function registerHarnessAskSayOptions(command: Command): Command {
253
256
  .option('--target <path>', 'self: harness가 작업할 레포 또는 디렉터리(self-mission 전용)')
254
257
  .option('--yes', 'git 아닌 프로젝트에 git init 및 첫 커밋을 만들도록 동의')
255
258
  .option('--correlation <id>', '요청과 런을 잇는 불투명 correlation 값')
259
+ .addOption(new Option('--seat <seat>', '런 귀속 자리 (OP|TC|MK|UX)').choices(['OP', 'TC', 'MK', 'UX']))
256
260
  .addOption(new Option('--goal-type <type>', `골 종류 (${GOAL_TYPES.join('|')})`).choices([...GOAL_TYPES]))
257
261
  .option('--force-preflight', '전제 검사 막힘을 명시 요청으로 우회(관측에 남음)')
258
262
  .option('--force-gate', '같은 골 실행 중 막힘을 명시 요청으로 우회(관측에 남음; 예산 막힘은 우회 불가)')
@@ -284,6 +288,7 @@ function registerHarnessMissionOptions(command: Command): Command {
284
288
  function normalizeHarnessCommonOptions(opts: HarnessAskSayOptions): HarnessAskSayOptions {
285
289
  return {
286
290
  ...(opts.json ? { json: true } : {}),
291
+ ...(opts.seat !== undefined ? { seat: opts.seat } : {}),
287
292
  ...(opts.base !== undefined ? { base: opts.base } : {}),
288
293
  ...(opts.target !== undefined ? { target: opts.target } : {}),
289
294
  ...(opts.yes === true ? { yes: true } : {}),
@@ -328,7 +333,7 @@ function assertHarnessChildLlmModel(opts: HarnessAskSayChildLlmOptions): void {
328
333
  }
329
334
 
330
335
  /** Shared post-parse gate: validate child model before any ask/say early return (including `--dry-run`). */
331
- type HarnessSubstrateOpts = { substrate?: 'local' | 'pod'; podPool?: string; podMemory?: string; after?: string; autoMerge?: boolean; base?: string; json?: boolean; target?: string; source?: string };
336
+ type HarnessSubstrateOpts = { substrate?: 'local' | 'pod'; podPool?: string; podMemory?: string; after?: string; autoMerge?: boolean; base?: string; json?: boolean; target?: string; source?: string; seat?: QueueSeat };
332
337
  function resolveLaunchSubstrate(opts: HarnessSubstrateOpts): ResolvedHarnessSubstrate {
333
338
  const resolved = resolveHarnessSubstrate({ flag: opts, config: getUserConfig(), env: process.env });
334
339
  debug.log('harness.substrate', 'resolved', resolved);
@@ -338,7 +343,17 @@ function resolveLaunchSubstrate(opts: HarnessSubstrateOpts): ResolvedHarnessSubs
338
343
  return resolved;
339
344
  }
340
345
  type PodExit = { status: number | null; signal?: string | null };
341
- type PodExitReason = 'signal' | 'human-stop' | 'supervisor' | 'no-launch' | 'unknown';
346
+ type PodExitReason = 'signal' | 'human-stop' | 'supervisor' | 'no-launch' | 'unknown'
347
+ | 'pod-error' | 'launch-gate-blocked' | 'ENOBUFS' | 'OOMKilled';
348
+
349
+ function unknownPodExitTailReason(output: string): PodExitReason | undefined {
350
+ const tail = output.slice(-16_000);
351
+ if (/\bOOMKilled\b/i.test(tail)) return 'OOMKilled';
352
+ if (/\bENOBUFS\b/i.test(tail)) return 'ENOBUFS';
353
+ if (/launch gate[^\r\n]*blocked|blocked[^\r\n]*launch gate/i.test(tail)) return 'launch-gate-blocked';
354
+ if (/\bpod-error\b/i.test(tail)) return 'pod-error';
355
+ return undefined;
356
+ }
342
357
  /** GitHub lists at most this many files for one pull request; a list that reaches it is not provably complete. */
343
358
  export const GITHUB_PR_FILES_LIST_CAP = 3000;
344
359
  const STOPPING_SUPERVISOR_VERDICTS = new Set(['UNCONVERGEABLE', 'CONTRACT-CONFLICT']);
@@ -441,34 +456,47 @@ export function classifyHarnessPodExit(
441
456
  try { applied = (deps.hasJobApplied ?? hasHarnessPodJobApplied)(runId, output); } catch { /* unknown */ }
442
457
  const noLaunch = ledgerReadable && !ledger?.some((entry) => LAUNCH_EVIDENCE_EVENTS.has(entry.event))
443
458
  && applied === false && !pr;
444
- reason = noLaunch ? 'no-launch' : 'unknown';
459
+ reason = noLaunch ? 'no-launch' : unknownPodExitTailReason(output) ?? 'unknown';
445
460
  line = noLaunch
446
461
  ? 'Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라'
447
- : 'Pod 실행 여부 또는 종료 이유를 확인하지 못했다';
462
+ : reason === 'unknown' ? 'Pod 실행 여부 또는 종료 이유를 확인하지 못했다'
463
+ : `Pod 종료 출력에서 확인한 원인: ${reason}`;
448
464
  }
449
465
  try { debug.log('harness.pod', 'exit-classified', { runId, reason, status: exit.status, signal: signal ?? null }); } catch { /* observation is fail-soft */ }
450
466
  return { reason, lines: [line, ...(pr ? [`PR 이 남아 있다: ${pr}`] : [])] };
451
467
  }
452
468
 
453
469
  /** The classified status is the same exit handed to the launcher; ledger failure is never an exit failure. */
454
- export function recordClassifiedHarnessPodExit(runId: string | undefined, reason: string, status: number): void {
470
+ export function recordClassifiedHarnessPodExit(runId: string | undefined, reason: string, status: number,
471
+ context: { entrance?: string; seat?: string; output?: string } = {}): void {
455
472
  if (!runId) return;
456
473
  try {
457
474
  const signal = status > 128
458
475
  ? Object.entries(osConstants.signals).find(([, number]) => number === status - 128)?.[0] ?? null : null;
459
- recordRunExit({ runId, reason, status, signal, at: new Date().toISOString() }, effectiveInstanceRoot());
476
+ const cwd = process.cwd();
477
+ const seat = context.seat ?? process.env.ELANOUS_HARNESS_SEAT;
478
+ recordRunExit({ runId, reason, status, signal, at: new Date().toISOString(),
479
+ ...(context.entrance ? { entrance: context.entrance } : {}),
480
+ ...(['OP', 'TC', 'MK', 'UX'].includes(seat ?? '') ? { seat } : {}),
481
+ cwd: `${basename(dirname(cwd))}/${basename(cwd)}`, hostname: hostname().split('.')[0]!, pid: process.pid,
482
+ ...(context.output ? { lastLines: incidentLastLines(context.output) } : {}),
483
+ }, effectiveInstanceRoot());
460
484
  } catch { /* incident writes cannot change Pod output or exit */ }
461
485
  }
462
486
 
463
487
  /** Every Pod launch entrance (harness ask/say · dev ask --substrate pod) warns once after a recent burst; it never blocks the launch. */
464
488
  export function warnRecentIncidentBurst(
465
489
  root: string = effectiveInstanceRoot(), now: Date = new Date(), write: (line: string) => void = (line) => console.error(line),
490
+ context: { seat?: string; entrance?: string } = {},
466
491
  ): boolean {
467
492
  try {
468
- const burst = recentBurst(root, now);
493
+ const seat = context.seat ?? process.env.ELANOUS_HARNESS_SEAT;
494
+ const burst = recentBurst(root, now, { entrance: context.entrance,
495
+ ...(['OP', 'TC', 'MK', 'UX'].includes(seat ?? '') ? { seat } : {}) });
469
496
  if (!burst) return false;
470
497
  write(formatIncidentBurstWarning(burst));
471
- try { debug.log('harness.incident', 'burst', { reason: burst.reason, count: burst.count }); } catch { /* observation is fail-soft */ }
498
+ try { debug.log('harness.incident', 'burst', { reason: burst.reason, count: burst.count,
499
+ ...(burst.seat ? { seat: burst.seat } : {}), ...(burst.entrance ? { entrance: burst.entrance } : {}) }); } catch { /* observation is fail-soft */ }
472
500
  return true;
473
501
  } catch { return false; /* incident reads cannot block a launch */ }
474
502
  }
@@ -540,7 +568,8 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
540
568
  process.exitCode = 2;
541
569
  return;
542
570
  }
543
- warnRecentIncidentBurst();
571
+ warnRecentIncidentBurst(effectiveInstanceRoot(), new Date(), (line) => console.error(line),
572
+ { entrance, seat: o.seat });
544
573
  let dispatchRecorded = false;
545
574
  try {
546
575
  const goalText = entrance === 'cli-harness-ask' ? readFileSync(input, 'utf8') : input;
@@ -559,14 +588,15 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
559
588
  const { dispatchHarnessOnPod } = await import('./harness-pod-dispatch.js');
560
589
  let output = '';
561
590
  let runId: string | undefined;
562
- const status = await dispatchHarnessOnPod({ entrance, input, podPool: pool, ...(dispatchRecorded ? { dispatchRecorded: true } : {}), ...(o.podMemory ? { podMemory: o.podMemory } : {}), ...(o.after ? { after: o.after } : {}), ...(o.autoMerge === false ? { autoMerge: false } : {}), ...(o.base ? { base: o.base } : {}), ...(o.json ? { json: true } : {}), ...(o.source ? { source: o.source } : {}) },
591
+ const status = await dispatchHarnessOnPod({ entrance, input, podPool: pool, ...(dispatchRecorded ? { dispatchRecorded: true } : {}), ...(o.podMemory ? { podMemory: o.podMemory } : {}), ...(o.after ? { after: o.after } : {}), ...(o.autoMerge === false ? { autoMerge: false } : {}), ...(o.base ? { base: o.base } : {}), ...(o.json ? { json: true } : {}), ...(o.source ? { source: o.source } : {}), ...(o.seat ? { seat: o.seat } : {}) },
563
592
  { onOutput: (text) => {
564
593
  runId ??= harnessPodRunId(output + text);
565
594
  output = (output + text).slice(-16_000);
566
595
  } });
567
596
  if (status !== 0) {
568
597
  const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
569
- recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status);
598
+ recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status,
599
+ { entrance, seat: o.seat, output });
570
600
  for (const line of classified.lines) console.error(line);
571
601
  process.exitCode = status;
572
602
  }
@@ -634,7 +664,7 @@ async function dispatchHarnessAskSay(
634
664
  readonly wouldStart: string;
635
665
  readonly goalPath?: string;
636
666
  },
637
- dispatch: (resolved: ResolvedHarnessSubstrate) => Promise<void>,
667
+ dispatch: (resolved: ResolvedHarnessSubstrate, stampedOpts: HarnessAskSayChildLlmOptions & HarnessDryRunOpts) => Promise<void>,
638
668
  gate: PreLaunchGateDeps & { readBudget?: () => Promise<BudgetDecision | 'unknown'> } = {},
639
669
  ): Promise<void> {
640
670
  await runInjectedHarnessHandler(async () => {
@@ -644,6 +674,25 @@ async function dispatchHarnessAskSay(
644
674
  printHarnessLaunchDryRun(dryRunPreview);
645
675
  return;
646
676
  }
677
+ // Resolve the launch directory once; descendants and the checkpoint retain the same identity.
678
+ const queued = process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && process.env.ELANOUS_HARNESS_SEAT;
679
+ const queueSeat = queued === 'OP' || queued === 'TC' || queued === 'MK' || queued === 'UX' ? queued : undefined;
680
+ const assigned = opts.seat ?? queueSeat ?? queueSeatForCwd(process.cwd(), getUserConfig().loops?.orchestrator?.seatTrees);
681
+ const inheritedSeat = process.env.ELANOUS_HARNESS_SEAT;
682
+ const podSeat = resolved.substrate === 'pod' && ['OP', 'TC', 'MK', 'UX'].includes(inheritedSeat ?? '')
683
+ ? inheritedSeat as QueueSeat : undefined;
684
+ const stampedSeat = resolved.substrate === 'pod' ? opts.seat ?? queueSeat ?? podSeat ?? assigned : assigned;
685
+ const stampedOpts = stampedSeat ? { ...opts, seat: stampedSeat } : opts;
686
+ const launch = async () => {
687
+ const previous = process.env.ELANOUS_HARNESS_SEAT;
688
+ if (stampedSeat) process.env.ELANOUS_HARNESS_SEAT = stampedSeat;
689
+ else delete process.env.ELANOUS_HARNESS_SEAT;
690
+ try { await dispatch(resolved, stampedOpts); }
691
+ finally {
692
+ if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
693
+ else process.env.ELANOUS_HARNESS_SEAT = previous;
694
+ }
695
+ };
647
696
  // Without a GoalId, match the request to a live run's exact verbatim original ask; still check budget.
648
697
  let identity = dryRunPreview.input;
649
698
  let goalId: string | null = null;
@@ -658,7 +707,7 @@ async function dispatchHarnessAskSay(
658
707
  // CLI wiring tests time out on them. Gate tests inject deps or opt in with ELANOUS_LAUNCH_GATE_LIVE=1.
659
708
  if (process.env.NODE_ENV === 'test' && Object.keys(gate).length === 0 && process.env.ELANOUS_LAUNCH_GATE_LIVE !== '1') {
660
709
  try { debug.log('execution-loop.launch-gate', 'skipped-test-env', { goalId }); } catch { /* observation is fail-soft */ }
661
- await dispatch(resolved);
710
+ await launch();
662
711
  return;
663
712
  }
664
713
  const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
@@ -673,7 +722,7 @@ async function dispatchHarnessAskSay(
673
722
  ...(decision.budget === 'unknown' ? ['budget unknown'] : decision.budget.action === 'next-provider' ? [decision.budget.reasons.join(' · ') || 'next-provider'] : []),
674
723
  ];
675
724
  if (warnings.length > 0) console.error(`⚠️ launch gate: ${warnings.join(' · ').split(/\r?\n/, 1)[0]}`);
676
- await dispatch(resolved);
725
+ await launch();
677
726
  });
678
727
  }
679
728
 
@@ -1606,6 +1655,30 @@ const executeGh: GhExecute = (args) => execFileSync('gh', args, {
1606
1655
  stdio: ['ignore', 'pipe', 'pipe'], env: process.env,
1607
1656
  });
1608
1657
 
1658
+ /** Merge-time adapter: reuse the sweep's GitHub inventory and worktree liveness without a second triage implementation. */
1659
+ export async function supersedeMergedGoalDrafts(
1660
+ number: number, cwd: string,
1661
+ execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand,
1662
+ ): Promise<void> {
1663
+ const remote = git(cwd, ['config', '--get', 'remote.origin.url']);
1664
+ const repository = remote.status === 0 ? githubRemoteRepository(remote.stdout) : undefined;
1665
+ if (!repository) throw new Error('Merge repository unavailable');
1666
+ // The PR can change between open and merge; never authorize a close using locally reconstructed metadata.
1667
+ const pr = ghJson<GithubPull>(['api', `repos/${repository}/pulls/${number}`], execute);
1668
+ if (!pr || pr.number !== number || pr.state !== 'closed' || !pr.merged_at || !pr.title?.trim()
1669
+ || !pr.head?.ref?.trim() || !pr.created_at || !Array.isArray(pr.labels)
1670
+ || !Number.isFinite(Date.parse(pr.created_at)) || !Number.isFinite(Date.parse(pr.merged_at))
1671
+ || Date.parse(pr.merged_at) < Date.parse(pr.created_at)) {
1672
+ throw new Error('Merged PR metadata unavailable');
1673
+ }
1674
+ const merged: SweepMergedPr & { createdAt: string; mergedAt: string } = {
1675
+ number: pr.number, title: pr.title, body: pr.body ?? undefined, branch: pr.head.ref,
1676
+ createdAt: pr.created_at, mergedAt: pr.merged_at,
1677
+ };
1678
+ const { supersedeDraftsOnMerge } = await import('../self-dev/draft-sweep.js');
1679
+ await supersedeDraftsOnMerge({ repository, merged, adapters: githubDraftSweepAdapters(execute, git, cwd) });
1680
+ }
1681
+
1609
1682
  function ghJson<T>(args: string[], execute: GhExecute): T {
1610
1683
  return JSON.parse(execute(args)) as T;
1611
1684
  }
@@ -1670,7 +1743,7 @@ function githubRemoteRepository(url: string): string | undefined {
1670
1743
  return match?.[1];
1671
1744
  }
1672
1745
 
1673
- export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand): DraftSweepAdapters {
1746
+ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd()): DraftSweepAdapters {
1674
1747
  const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
1675
1748
  // ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
1676
1749
  // Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
@@ -1763,6 +1836,7 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
1763
1836
  return draftSweepRunStatus(ledgerMatches, running, repository, draft.number);
1764
1837
  };
1765
1838
  return {
1839
+ getPrFiles: async (repository, number) => filesFor(repository, number),
1766
1840
  listDrafts: async (page, perPage, repository): Promise<SweepDraft[]> =>
1767
1841
  (await listed(open, repository, 'open')).filter((pr) => pr.draft)
1768
1842
  .slice((page - 1) * perPage, page * perPage)
@@ -1811,7 +1885,6 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
1811
1885
  } finally { store.close(); }
1812
1886
  },
1813
1887
  listLiveBranches: async (repository) => {
1814
- const cwd = process.cwd();
1815
1888
  const remote = git(cwd, ['config', '--get', 'remote.origin.url']);
1816
1889
  if (remote.status !== 0 || githubRemoteRepository(remote.stdout)?.toLowerCase() !== repository.toLowerCase()) {
1817
1890
  return undefined;
@@ -1920,6 +1993,55 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
1920
1993
  });
1921
1994
  }
1922
1995
 
1996
+ function renderHarnessGoal(result: GoalLookupResult, universe: '운영' | '시험'): string {
1997
+ const matchedRun = result.runState?.results.find((entry) => entry.runId === result.runId);
1998
+ return [
1999
+ `골: ${result.goalBody?.split(/\r?\n/, 1)[0] || '골 문서에 없음'}`,
2000
+ `runId: ${result.runId ?? '원장에 없음'}`,
2001
+ `칸 id: ${result.kanId ?? '기록 없음'}`,
2002
+ `PR: ${result.record?.prNumber != null ? `#${result.record.prNumber}` : '기록 없음'}`,
2003
+ `stage/결과: ${result.record?.stage ?? '기록 없음'} / ${result.record?.outcome ?? '기록 없음'}`,
2004
+ `종결 사유: ${result.runState?.supervisorStopReason ?? matchedRun?.error?.message ?? '기록 없음'}`,
2005
+ `찾은 우주: ${universe}`,
2006
+ `원장: ${result.runState ? '찾음' : '원장에 없음'}`,
2007
+ `골 문서: ${result.goalFile ? '찾음' : '골 문서에 없음'}`,
2008
+ ].join('\n');
2009
+ }
2010
+
2011
+ function installHarnessGoalCommand(harnessCmd: Command, options?: GoalLookupOptions, archiveOptions?: GoalArchiveOptions): Command {
2012
+ const goal = harnessCmd.command('goal').usage('<reference> | archive [--before <date>] [--apply]')
2013
+ .description('골 문서와 실행 결과를 조회하거나 보관한다');
2014
+ goal.command('archive')
2015
+ .description('종료된 골 문서를 미리 보거나 상태 루트에 보관한다 — 저장소는 건드리지 않고 지울 목록(filesForPr)만 낸다')
2016
+ .option('--before <date>', '완료 시각이 이 YYYY-MM-DD 이전인 골만')
2017
+ .option('--apply', '상태 루트에 보관 복사·색인 (저장소 삭제·스테이지·커밋 없음 · 지우기는 filesForPr 로 사람이 PR)')
2018
+ .option('--trust-doc-record', '런 원장이 없을 때 문서에 적힌 종료 실행 기록을 신뢰')
2019
+ .action((opts: { before?: string; apply?: boolean; trustDocRecord?: boolean }) => {
2020
+ try {
2021
+ const result = archiveGoals({ ...archiveOptions, before: opts.before, apply: opts.apply, trustDocRecord: opts.trustDocRecord });
2022
+ console.log(JSON.stringify(result));
2023
+ } catch (error) {
2024
+ console.error(humanErrorLine(error));
2025
+ process.exitCode = 1;
2026
+ }
2027
+ });
2028
+ goal.command('show <reference>', { isDefault: true })
2029
+ .description('PR 번호 또는 runId 로 골 문서와 실행 결과를 조회한다 (읽기 전용)')
2030
+ .option('--json', 'GoalLookupResult 그대로 출력')
2031
+ .action((reference: string, opts: { json?: boolean }) => {
2032
+ try {
2033
+ const result = lookupGoal(reference, options);
2034
+ const universe = effectiveInstanceRoot() === prodInstanceRoot() ? '운영' : '시험';
2035
+ console.log(opts.json ? JSON.stringify(result) : renderHarnessGoal(result, universe));
2036
+ if (!result.goalFile || !result.runState) process.exitCode = 1;
2037
+ } catch (error) {
2038
+ console.error(humanErrorLine(error));
2039
+ process.exitCode = 1;
2040
+ }
2041
+ });
2042
+ return goal;
2043
+ }
2044
+
1923
2045
  function installHarnessBudgetCommand(harnessCmd: Command): Command {
1924
2046
  return harnessCmd
1925
2047
  .command('budget')
@@ -2106,6 +2228,8 @@ export interface HarnessCliCommandDeps {
2106
2228
  processObservation?: HarnessProcessObservationDeps;
2107
2229
  draftSweep?: HarnessDraftSweepDeps;
2108
2230
  queue?: HarnessQueueDeps;
2231
+ goalLookup?: GoalLookupOptions;
2232
+ goalArchive?: GoalArchiveOptions;
2109
2233
  ask?: HarnessAskHandler;
2110
2234
  say?: HarnessSayHandler;
2111
2235
  /** Observe-only Pod host dispatch seam. */
@@ -2146,8 +2270,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2146
2270
  const bursts = detectBursts(rows).filter((burst) => Date.parse(burst.lastAt) >= now - since * 60_000 && Date.parse(burst.lastAt) <= now);
2147
2271
  if (opts.json) console.log(JSON.stringify({ rows: selected, bursts }));
2148
2272
  else {
2149
- for (const row of selected) console.log(`${row.at} run ${row.runId} · ${row.reason} · status=${row.status} signal=${row.signal ?? '-'}`);
2150
- for (const burst of bursts) console.log(`${burst.firstAt}–${burst.lastAt} 묶음 ${burst.reason} ×${burst.count} · ${burst.runIds.join(', ')}`);
2273
+ for (const row of selected) console.log(`${row.at} run ${row.runId} · seat=${row.seat ?? '-'} entrance=${row.entrance ?? '-'} reason=${row.reason} · status=${row.status} signal=${row.signal ?? '-'}`);
2274
+ for (const burst of bursts) console.log(`${burst.firstAt}–${burst.lastAt} 묶음 ${burst.reason} ×${burst.count} · seat=${burst.seat ?? '-'} entrance=${burst.entrance ?? '-'} · ${burst.runIds.join(', ')}`);
2151
2275
  }
2152
2276
  } catch (error) {
2153
2277
  console.error(humanErrorLine(error));
@@ -2157,6 +2281,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2157
2281
  installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
2158
2282
  installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
2159
2283
  installHarnessBudgetCommand(harnessCmd);
2284
+ installHarnessGoalCommand(harnessCmd, deps.goalLookup, deps.goalArchive);
2160
2285
  installHarnessDraftSweepCommand(harnessCmd, deps.draftSweep);
2161
2286
  const queue = harnessCmd.command('queue').description('자리별 영속 발사 대기열');
2162
2287
  const queueDeps = deps.queue ?? {};
@@ -2197,13 +2322,13 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2197
2322
  ...(opts.goalType !== undefined ? { goalType: opts.goalType } : {}),
2198
2323
  ...(opts.target !== undefined ? { target: opts.target } : {}),
2199
2324
  },
2200
- (resolved) => {
2325
+ (resolved, stampedOpts) => {
2201
2326
  if (resolved.substrate !== 'pod' && (opts as { source?: string }).source !== undefined) {
2202
2327
  console.error('`--source` 는 `--substrate pod` 와 함께');
2203
2328
  process.exitCode = 2;
2204
2329
  return Promise.resolve();
2205
2330
  }
2206
- return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-ask', goalPath, resolved.pool!, deps.podDispatchTask) : ask(goalPath, normalizeHarnessAskSayOptions(opts));
2331
+ return resolved.substrate === 'pod' ? onPod(stampedOpts, 'cli-harness-ask', goalPath, resolved.pool!, deps.podDispatchTask) : ask(goalPath, normalizeHarnessAskSayOptions(stampedOpts));
2207
2332
  },
2208
2333
  deps.launchGate,
2209
2334
  );
@@ -2222,13 +2347,13 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2222
2347
  wouldStart: '골 문서 · 워크트리 · 브랜치 · 자식 · 파이프라인',
2223
2348
  ...(opts.target !== undefined ? { target: opts.target } : {}),
2224
2349
  },
2225
- (resolved) => {
2350
+ (resolved, stampedOpts) => {
2226
2351
  if (resolved.substrate !== 'pod' && (opts as { source?: string }).source !== undefined) {
2227
2352
  console.error('`--source` 는 `--substrate pod` 와 함께');
2228
2353
  process.exitCode = 2;
2229
2354
  return Promise.resolve();
2230
2355
  }
2231
- return resolved.substrate === 'pod' ? onPod(opts, 'cli-harness-say', sentence.join(' '), resolved.pool!, deps.podDispatchTask) : say(sentence, normalizeHarnessAskSayOptions(opts));
2356
+ return resolved.substrate === 'pod' ? onPod(stampedOpts, 'cli-harness-say', sentence.join(' '), resolved.pool!, deps.podDispatchTask) : say(sentence, normalizeHarnessAskSayOptions(stampedOpts));
2232
2357
  },
2233
2358
  deps.launchGate,
2234
2359
  );
@@ -1,6 +1,7 @@
1
1
  import { Database } from 'bun:sqlite';
2
2
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import { redactSecretText } from '../debug/log.js';
4
5
 
5
6
  export interface RunExit {
6
7
  runId: string;
@@ -8,6 +9,12 @@ export interface RunExit {
8
9
  status: number | null;
9
10
  signal: string | null;
10
11
  at: string;
12
+ entrance?: string;
13
+ seat?: string;
14
+ cwd?: string;
15
+ hostname?: string;
16
+ pid?: number;
17
+ lastLines?: string[];
11
18
  }
12
19
 
13
20
  export interface IncidentBurst {
@@ -16,6 +23,14 @@ export interface IncidentBurst {
16
23
  firstAt: string;
17
24
  lastAt: string;
18
25
  runIds: string[];
26
+ entrance?: string;
27
+ seat?: string;
28
+ }
29
+
30
+ /** Scrub before bounding: a token crossing the 200-character boundary must not leak. */
31
+ export function incidentLastLines(output: string): string[] {
32
+ return output.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-3)
33
+ .map((line) => redactSecretText(line).slice(0, 200));
19
34
  }
20
35
 
21
36
  const KST_OFFSET_MS = 9 * 60 * 60_000;
@@ -52,7 +67,15 @@ export function readRunExits(root: string): RunExit[] {
52
67
  || !(row.status === null || typeof row.status === 'number')
53
68
  || !(row.signal === null || typeof row.signal === 'string')) continue;
54
69
  seen.add(row.runId);
55
- rows.push(row as RunExit);
70
+ rows.push({ runId: row.runId, reason: row.reason, status: row.status, signal: row.signal, at: row.at,
71
+ ...(typeof row.entrance === 'string' ? { entrance: row.entrance } : {}),
72
+ ...(typeof row.seat === 'string' ? { seat: row.seat } : {}),
73
+ ...(typeof row.cwd === 'string' ? { cwd: row.cwd } : {}),
74
+ ...(typeof row.hostname === 'string' ? { hostname: row.hostname } : {}),
75
+ ...(typeof row.pid === 'number' && Number.isSafeInteger(row.pid) ? { pid: row.pid } : {}),
76
+ ...(Array.isArray(row.lastLines) && row.lastLines.every((line) => typeof line === 'string')
77
+ ? { lastLines: row.lastLines } : {}),
78
+ });
56
79
  }
57
80
  }
58
81
  return rows.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
@@ -71,7 +94,10 @@ export function recordRunExit(row: RunExit, root: string): void {
71
94
  lock.exec('BEGIN IMMEDIATE');
72
95
  try {
73
96
  if (!readRunExits(root).some((record) => record.runId === row.runId)) {
74
- appendFileSync(join(dir, `${kstDate(at)}.jsonl`), `${JSON.stringify(row)}\n`);
97
+ const safeRow = row.lastLines === undefined ? row : {
98
+ ...row, lastLines: row.lastLines.slice(-3).map((line) => redactSecretText(line).slice(0, 200)),
99
+ };
100
+ appendFileSync(join(dir, `${kstDate(at)}.jsonl`), `${JSON.stringify(safeRow)}\n`);
75
101
  }
76
102
  lock.exec('COMMIT');
77
103
  } catch (error) {
@@ -83,21 +109,23 @@ export function recordRunExit(row: RunExit, root: string): void {
83
109
  }
84
110
  }
85
111
 
86
- /** Non-overlapping, maximal same-reason windows; no run contributes to two bursts. */
112
+ /** Non-overlapping, maximal same-reason/seat/entrance windows; no run contributes to two bursts. */
87
113
  export function detectBursts(rows: readonly RunExit[], { windowMs = 60_000, threshold = 3 }: {
88
114
  windowMs?: number; threshold?: number;
89
115
  } = {}): IncidentBurst[] {
90
116
  const bursts: IncidentBurst[] = [];
91
- const byReason = new Map<string, RunExit[]>();
117
+ const byIdentity = new Map<string, RunExit[]>();
92
118
  const seen = new Set<string>();
93
119
  for (const row of rows) {
94
120
  if (!row.runId || seen.has(row.runId) || !Number.isFinite(Date.parse(row.at))) continue;
95
121
  seen.add(row.runId);
96
- const group = byReason.get(row.reason) ?? [];
122
+ const key = JSON.stringify([row.reason, row.seat ?? null, row.entrance ?? null]);
123
+ const group = byIdentity.get(key) ?? [];
97
124
  group.push(row);
98
- byReason.set(row.reason, group);
125
+ byIdentity.set(key, group);
99
126
  }
100
- for (const [reason, group] of byReason) {
127
+ for (const group of byIdentity.values()) {
128
+ const { reason, seat, entrance } = group[0]!;
101
129
  group.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
102
130
  for (let start = 0; start < group.length;) {
103
131
  let end = start + 1;
@@ -105,25 +133,28 @@ export function detectBursts(rows: readonly RunExit[], { windowMs = 60_000, thre
105
133
  if (end - start < threshold) { start++; continue; }
106
134
  const members = group.slice(start, end);
107
135
  bursts.push({ reason, count: members.length, firstAt: members[0]!.at,
108
- lastAt: members[members.length - 1]!.at, runIds: members.map((row) => row.runId) });
136
+ lastAt: members[members.length - 1]!.at, runIds: members.map((row) => row.runId),
137
+ ...(seat ? { seat } : {}), ...(entrance ? { entrance } : {}) });
109
138
  start = end;
110
139
  }
111
140
  }
112
141
  return bursts.sort((a, b) => Date.parse(a.firstAt) - Date.parse(b.firstAt) || a.reason.localeCompare(b.reason));
113
142
  }
114
143
 
115
- export function recentBurst(root: string, now: Date | number, { lookbackMs = 15 * 60_000 }: {
116
- lookbackMs?: number;
144
+ export function recentBurst(root: string, now: Date | number, { lookbackMs = 15 * 60_000, seat, entrance }: {
145
+ lookbackMs?: number; seat?: string; entrance?: string;
117
146
  } = {}): IncidentBurst | null {
118
147
  const time = typeof now === 'number' ? now : now.getTime();
119
148
  const recent = detectBursts(readRunExits(root)).filter((burst) => {
120
149
  const last = Date.parse(burst.lastAt);
121
- return last <= time && last >= time - lookbackMs;
150
+ return last <= time && last >= time - lookbackMs
151
+ && burst.seat === seat && burst.entrance === entrance;
122
152
  });
123
153
  return recent.sort((a, b) => Date.parse(a.lastAt) - Date.parse(b.lastAt)).at(-1) ?? null;
124
154
  }
125
155
 
126
156
  export function formatIncidentBurstWarning(burst: IncidentBurst): string {
127
157
  const time = new Date(Date.parse(burst.lastAt) + KST_OFFSET_MS).toISOString().slice(11, 16);
128
- return `⚠ 최근 묶음 사고: ${burst.reason} ×${burst.count} (${time} KST) — 원인 확인 전 재발사는 중복·재사고 위험 · elanous harness incidents`;
158
+ const owner = burst.seat || burst.entrance ? ` [seat=${burst.seat ?? '-'} entrance=${burst.entrance ?? '-'}]` : '';
159
+ return `⚠ 최근 묶음 사고${owner}: ${burst.reason} ×${burst.count} (${time} KST) — 원인 확인 전 재발사는 중복·재사고 위험 · elanous harness incidents`;
129
160
  }
@@ -20,6 +20,7 @@ export interface HarnessPodDispatchInput {
20
20
  /** say 의 문장, 또는 ask 의 골 문서 경로. */
21
21
  readonly input: string;
22
22
  readonly podPool?: string;
23
+ readonly seat?: 'OP' | 'TC' | 'MK' | 'UX';
23
24
  /** Host already recorded the observe-only dispatch; suppress a second decision in the Pod. */
24
25
  readonly dispatchRecorded?: boolean;
25
26
  /** Pod 메모리 등급(standard|high) — 오케스트레이터 환경 `ELANOUS_POD_MEMORY_TIER` 로 Job 까지 간다. */
@@ -80,6 +81,7 @@ export function dispatchHarnessOnPod(input: HarnessPodDispatchInput, deps: PodDi
80
81
  const args = podOrchestrateArgs(input, goalFile);
81
82
  debug.log('harness.substrate', 'goal-file', { entrance: input.entrance, mode: tempDir ? 'temp' : 'path' });
82
83
  const env = { ...process.env };
84
+ if (input.seat) env.ELANOUS_HARNESS_SEAT = input.seat;
83
85
  delete env.ELANOUS_POD_GOAL_DOC;
84
86
  delete env.ELANOUS_DISPATCH_RECORDED;
85
87
  if (input.dispatchRecorded) env.ELANOUS_DISPATCH_RECORDED = '1';
@@ -3,9 +3,9 @@ import { randomUUID } from 'node:crypto';
3
3
  import { renameSync, writeFileSync } from 'node:fs';
4
4
 
5
5
  /** Only a confirmed terminal observation allows reconciliation to release an uncertain launch. */
6
- export function writeHarnessQueueReceipt(receiptPath: string, state: 'started' | 'finished' | 'not-started'): void {
6
+ export function writeHarnessQueueReceipt(receiptPath: string, state: 'started' | 'finished' | 'not-started', exitCode?: number): void {
7
7
  const temp = `${receiptPath}.${randomUUID()}.tmp`;
8
- writeFileSync(temp, JSON.stringify({ state, at: new Date().toISOString() }), { flag: 'wx', mode: 0o600 });
8
+ writeFileSync(temp, JSON.stringify({ state, at: new Date().toISOString(), ...(exitCode === undefined ? {} : { exitCode }) }), { flag: 'wx', mode: 0o600 });
9
9
  renameSync(temp, receiptPath);
10
10
  }
11
11
 
@@ -22,7 +22,7 @@ export async function runHarnessQueueChild(
22
22
  child.once('spawn', () => {
23
23
  started = true;
24
24
  record('started');
25
- child.once('exit', (code) => { record('finished'); done(code ?? 1); });
25
+ child.once('exit', (code) => { writeHarnessQueueReceipt(receiptPath, 'finished', code ?? 1); done(code ?? 1); });
26
26
  });
27
27
  child.once('error', () => {
28
28
  if (!started) { record('not-started'); done(1); }