elanous 0.2.20 → 0.2.22

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 (456) 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/aBi6EyMtLsrl7J8eH4kF7/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  11. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  13. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  19. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  25. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  26. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  34. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  38. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  45. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  47. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  48. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  55. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  60. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  62. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  64. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  65. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  67. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  74. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  75. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  76. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  82. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  83. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  85. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  87. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  89. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  90. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
  105. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
  138. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
  139. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  141. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  143. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  144. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  145. package/apps/pwa/out/approvals/index.html +1 -1
  146. package/apps/pwa/out/approvals/index.txt +14 -14
  147. package/apps/pwa/out/autopilot/index.html +1 -1
  148. package/apps/pwa/out/autopilot/index.txt +14 -14
  149. package/apps/pwa/out/board/index.html +1 -1
  150. package/apps/pwa/out/board/index.txt +14 -14
  151. package/apps/pwa/out/botlab/index.html +1 -1
  152. package/apps/pwa/out/botlab/index.txt +14 -14
  153. package/apps/pwa/out/bots/index.html +1 -1
  154. package/apps/pwa/out/bots/index.txt +14 -14
  155. package/apps/pwa/out/ceo/index.html +1 -1
  156. package/apps/pwa/out/ceo/index.txt +14 -14
  157. package/apps/pwa/out/chat/index.html +1 -1
  158. package/apps/pwa/out/chat/index.txt +14 -14
  159. package/apps/pwa/out/consult/index.html +1 -1
  160. package/apps/pwa/out/consult/index.txt +14 -14
  161. package/apps/pwa/out/control/index.html +1 -1
  162. package/apps/pwa/out/control/index.txt +14 -14
  163. package/apps/pwa/out/decisions/index.html +1 -1
  164. package/apps/pwa/out/decisions/index.txt +14 -14
  165. package/apps/pwa/out/design-check/index.html +1 -1
  166. package/apps/pwa/out/design-check/index.txt +14 -14
  167. package/apps/pwa/out/editor/index.html +1 -1
  168. package/apps/pwa/out/editor/index.txt +15 -15
  169. package/apps/pwa/out/exec/index.html +1 -1
  170. package/apps/pwa/out/exec/index.txt +14 -14
  171. package/apps/pwa/out/field/index.html +1 -1
  172. package/apps/pwa/out/field/index.txt +14 -14
  173. package/apps/pwa/out/healthz +1 -1
  174. package/apps/pwa/out/index.html +1 -1
  175. package/apps/pwa/out/index.txt +14 -14
  176. package/apps/pwa/out/inside/index.html +1 -1
  177. package/apps/pwa/out/inside/index.txt +15 -15
  178. package/apps/pwa/out/intake/index.html +1 -1
  179. package/apps/pwa/out/intake/index.txt +14 -14
  180. package/apps/pwa/out/live/index.html +1 -1
  181. package/apps/pwa/out/live/index.txt +14 -14
  182. package/apps/pwa/out/live-run/index.html +1 -0
  183. package/apps/pwa/out/live-run/index.txt +35 -0
  184. package/apps/pwa/out/loops/index.html +1 -1
  185. package/apps/pwa/out/loops/index.txt +14 -14
  186. package/apps/pwa/out/market/index.html +1 -1
  187. package/apps/pwa/out/market/index.txt +14 -14
  188. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  189. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  190. package/apps/pwa/out/morning/index.html +1 -1
  191. package/apps/pwa/out/morning/index.txt +14 -14
  192. package/apps/pwa/out/observatory/index.html +1 -1
  193. package/apps/pwa/out/observatory/index.txt +14 -14
  194. package/apps/pwa/out/ops/checklist/index.html +1 -1
  195. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  196. package/apps/pwa/out/ops/release/index.html +1 -1
  197. package/apps/pwa/out/ops/release/index.txt +14 -14
  198. package/apps/pwa/out/ops/seats/index.html +1 -1
  199. package/apps/pwa/out/ops/seats/index.txt +14 -14
  200. package/apps/pwa/out/outputs/index.html +1 -1
  201. package/apps/pwa/out/outputs/index.txt +14 -14
  202. package/apps/pwa/out/reflection/index.html +1 -1
  203. package/apps/pwa/out/reflection/index.txt +15 -15
  204. package/apps/pwa/out/scheduler/index.html +1 -1
  205. package/apps/pwa/out/scheduler/index.txt +14 -14
  206. package/apps/pwa/out/sessions/index.html +1 -1
  207. package/apps/pwa/out/sessions/index.txt +14 -14
  208. package/apps/pwa/out/settings/devices/index.html +1 -1
  209. package/apps/pwa/out/settings/devices/index.txt +14 -14
  210. package/apps/pwa/out/settings/index.html +1 -1
  211. package/apps/pwa/out/settings/index.txt +14 -14
  212. package/apps/pwa/out/setup/done/index.html +1 -1
  213. package/apps/pwa/out/setup/done/index.txt +14 -14
  214. package/apps/pwa/out/setup/index.html +1 -1
  215. package/apps/pwa/out/setup/index.txt +14 -14
  216. package/apps/pwa/out/share/index.html +1 -1
  217. package/apps/pwa/out/share/index.txt +14 -14
  218. package/apps/pwa/out/showroom/index.html +1 -1
  219. package/apps/pwa/out/showroom/index.txt +14 -14
  220. package/apps/pwa/out/tasks/index.html +1 -1
  221. package/apps/pwa/out/tasks/index.txt +14 -14
  222. package/apps/pwa/out/term/index.html +1 -1
  223. package/apps/pwa/out/term/index.txt +14 -14
  224. package/apps/pwa/out/today/index.html +1 -1
  225. package/apps/pwa/out/today/index.txt +14 -14
  226. package/apps/pwa/out/trace/index.html +1 -1
  227. package/apps/pwa/out/trace/index.txt +14 -14
  228. package/apps/pwa/out/vault/index.html +1 -1
  229. package/apps/pwa/out/vault/index.txt +14 -14
  230. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  231. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  232. package/apps/pwa/out/workflows/index.html +1 -1
  233. package/apps/pwa/out/workflows/index.txt +14 -14
  234. package/apps/pwa/out/workspace/index.html +1 -1
  235. package/apps/pwa/out/workspace/index.txt +14 -14
  236. package/apps/pwa/out/worktrees/index.html +1 -1
  237. package/apps/pwa/out/worktrees/index.txt +14 -14
  238. package/graphs/catalog/catalog.yaml +1 -1
  239. package/graphs/drafts/recipes.yaml +6 -6
  240. package/graphs/implement-loop.yaml +277 -5
  241. package/graphs/test-diet/recipes.yaml +3 -0
  242. package/graphs/test-diet/test-diet.yaml +3 -1
  243. package/package.json +1 -1
  244. package/scripts/coord-channel-watch.sh +31 -4
  245. package/scripts/docs-lint.ts +4 -1
  246. package/scripts/f-session-monitor.ts +69 -0
  247. package/scripts/hq/standby-snapshot.ts +7 -3
  248. package/scripts/knowledge-compress.ts +11 -0
  249. package/scripts/marketing/generate-ax-brief.ts +18 -1
  250. package/scripts/measure-nl-routing-corpus.ts +11 -3
  251. package/scripts/nightly-docops.ts +90 -3
  252. package/scripts/release-loop/gate-node.ts +5 -5
  253. package/scripts/release-loop/shard-plan.ts +50 -5
  254. package/scripts/research-coo-admin-dates.ts +39 -0
  255. package/scripts/rhythm/daily.ts +2 -1
  256. package/scripts/skills/public-check.ts +7 -2
  257. package/scripts/smart-merge/resync-shadow.ts +5 -3
  258. package/scripts/test-diet/lib.ts +81 -1
  259. package/scripts/test-diet/node.ts +48 -2
  260. package/scripts/verify-coo-admin-evidence.ts +83 -0
  261. package/src/acp/client.ts +4 -2
  262. package/src/agent/run-context.ts +4 -0
  263. package/src/agent-env/profile.ts +25 -5
  264. package/src/agent-mission/driver.ts +26 -12
  265. package/src/agent-mission/supervisor-metrics.ts +17 -2
  266. package/src/agent-substrate/acp-reviewer.ts +12 -5
  267. package/src/agent-substrate/pr-reviewer.ts +11 -4
  268. package/src/agent-substrate/review-intent.ts +102 -3
  269. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  270. package/src/autopilot/pr-manager.ts +18 -8
  271. package/src/away/release-watch.ts +47 -8
  272. package/src/briefing/brief-items.ts +104 -11
  273. package/src/browser-cdp/client.ts +10 -5
  274. package/src/chat/index.ts +1 -0
  275. package/src/cli/brief-cli.ts +20 -2
  276. package/src/cli/config-classify.ts +83 -0
  277. package/src/cli/control-cli.ts +14 -0
  278. package/src/cli/decisions-cli.ts +26 -16
  279. package/src/cli/logs-model-input.ts +1 -1
  280. package/src/cli/ops-cli.ts +11 -0
  281. package/src/cli/ops-overview-cli.ts +50 -0
  282. package/src/cli/pod-cli.ts +1 -0
  283. package/src/cli/pr-cli.ts +65 -8
  284. package/src/cli/release-cli.ts +21 -4
  285. package/src/cli/tasks-cli.ts +214 -1
  286. package/src/connectors/types.ts +6 -6
  287. package/src/context-bus/context-now-surfaces.ts +18 -0
  288. package/src/context-bus/coord-events.ts +32 -4
  289. package/src/context-bus/long-term-memory.ts +23 -6
  290. package/src/context-bus/nightly-condense.ts +169 -21
  291. package/src/control/control-status.ts +87 -0
  292. package/src/control-plane/light-member.ts +5 -3
  293. package/src/control-plane/member.ts +172 -7
  294. package/src/core-turn/run-goal-loop.ts +22 -1
  295. package/src/dashboard/index.ts +40 -4
  296. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  297. package/src/decisions/decision-cards.ts +20 -1
  298. package/src/decisions/decision-ledger.ts +11 -8
  299. package/src/decisions/web-push-decision-cards.ts +19 -3
  300. package/src/discord-self-message.ts +2 -2
  301. package/src/domains/coo-admin-dates.evidence.json +876 -0
  302. package/src/domains/coo-admin-tool.ts +48 -14
  303. package/src/domains/ops-status.ts +53 -0
  304. package/src/domains/outbound-alert.ts +24 -1
  305. package/src/esc-abort-gate.ts +3 -1
  306. package/src/exec-requests/planner.ts +6 -0
  307. package/src/graph-kinds/registry.ts +6 -6
  308. package/src/graph-runner/graph-cli.ts +91 -2
  309. package/src/graph-runner/graph-run-api.ts +20 -0
  310. package/src/graph-runner/graph-tick.ts +73 -12
  311. package/src/graph-runner/runner.ts +43 -11
  312. package/src/graph-unify/wf-to-graph.ts +79 -0
  313. package/src/graph-wizard/archetypes.ts +88 -0
  314. package/src/graph-wizard/generate.ts +577 -0
  315. package/src/graph-wizard/saved-steps.ts +120 -0
  316. package/src/graph-wizard/steps.ts +206 -0
  317. package/src/harness/harness-cli-command.ts +186 -32
  318. package/src/harness/harness-queue.ts +58 -14
  319. package/src/harness/launch-advice.ts +23 -0
  320. package/src/index.ts +43 -11
  321. package/src/intake-plane/digest.ts +18 -11
  322. package/src/knowledge/conclusion-compress.ts +93 -0
  323. package/src/knowledge/know-find.ts +9 -2
  324. package/src/knowledge/query.ts +20 -0
  325. package/src/llm.ts +26 -16
  326. package/src/loops/neighbor-runtime.ts +88 -0
  327. package/src/loops/orchestrator/author-depth.ts +29 -11
  328. package/src/loops/orchestrator/overlap.ts +7 -1
  329. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  330. package/src/loops/orchestrator/tick.ts +35 -2
  331. package/src/loops/orchestrator/traffic.ts +2 -0
  332. package/src/loops/registry.ts +5 -0
  333. package/src/market/publish.ts +17 -1
  334. package/src/maturity/feature-maturity.ts +4 -2
  335. package/src/mss/logging/redaction.ts +17 -0
  336. package/src/nexus/api/draft-metrics.ts +128 -0
  337. package/src/nexus/api/graph-peer-edit.ts +77 -0
  338. package/src/nexus/api/graph-wizard-api.ts +61 -0
  339. package/src/nexus/api/graphs-api.ts +193 -17
  340. package/src/nexus/api/harness-traversal.ts +205 -0
  341. package/src/nexus/api/http-server.ts +122 -6
  342. package/src/nexus/api/plugins-market.ts +24 -0
  343. package/src/nexus/index.ts +3 -2
  344. package/src/nexus/outbound/router.ts +28 -8
  345. package/src/oauth/claude-pty-maturity.ts +24 -0
  346. package/src/oauth/fallback-chain.ts +27 -14
  347. package/src/onboarding/first-run.ts +3 -0
  348. package/src/ops/overview-collect.ts +345 -0
  349. package/src/ops/overview-seat-era.ts +90 -0
  350. package/src/ops/overview.ts +223 -0
  351. package/src/plugins/maker/market-bundle.ts +9 -0
  352. package/src/plugins/maker/plugin-maker.ts +9 -3
  353. package/src/plugins/maker/wizard-generate.ts +73 -9
  354. package/src/policy/llm-policy.ts +11 -6
  355. package/src/release-loop/checklist-board.ts +27 -0
  356. package/src/release-loop/checklist.ts +38 -0
  357. package/src/release-loop/gate-shards.ts +7 -2
  358. package/src/seat-address/seat-address.ts +1 -0
  359. package/src/seat-loop/seat-loop.ts +174 -20
  360. package/src/seat-loop/seat-report.ts +29 -1
  361. package/src/self-dev/ask-launch-flow.ts +4 -2
  362. package/src/self-dev/ask-launch-io.ts +3 -1
  363. package/src/self-dev/dev-cli.ts +90 -8
  364. package/src/self-dev/dev-pipeline.ts +20 -1
  365. package/src/self-dev/draft-sweep.ts +45 -8
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/orchestrate.ts +3 -0
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-dev/review-provider-fallback.ts +83 -0
  372. package/src/self-dev/run-supervisor.ts +5 -1
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/goal-author.ts +14 -0
  375. package/src/self-implement/harness-policy-gates.ts +7 -5
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/host-regate.ts +28 -14
  378. package/src/self-implement/l8-merge-queue.ts +80 -8
  379. package/src/self-implement/orchestrator.ts +84 -28
  380. package/src/self-implement/seams.ts +8 -3
  381. package/src/self-implement/self-implement-cli.ts +4 -0
  382. package/src/self-implement/self-implement-runtime.ts +12 -0
  383. package/src/session-runtime/index.ts +5 -3
  384. package/src/shell-env-bootstrap.ts +35 -6
  385. package/src/steward/triage.ts +25 -8
  386. package/src/storage/file-lock.ts +33 -4
  387. package/src/task-agent/card-evidence.ts +6 -0
  388. package/src/task-agent/completion-evidence.ts +31 -3
  389. package/src/task-agent/cover.ts +61 -0
  390. package/src/task-agent/live-moves.ts +126 -3
  391. package/src/task-agent/parent-host.ts +315 -0
  392. package/src/task-agent/shadow.ts +19 -4
  393. package/src/task-agent/task-hand.ts +11 -2
  394. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  395. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  396. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  397. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  398. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  399. package/src/telegram-commands.ts +8 -0
  400. package/src/telegram-project-command.ts +6 -0
  401. package/src/telegram.ts +2 -2
  402. package/src/tool-runtime/browser-runtime.ts +87 -3
  403. package/src/tool-runtime/index.ts +1 -0
  404. package/src/typecheck-ratchet.ts +2 -1
  405. package/src/user-config.ts +55 -8
  406. package/src/version/packed-revision.json +1 -1
  407. package/src/workflow-runtime/executor.ts +24 -2
  408. package/src/workflow-runtime/triggers/registry.ts +27 -0
  409. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  410. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  416. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  425. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  454. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  455. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
  456. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -1,4 +1,4 @@
1
- import { execFile, execFileSync, spawn } from 'node:child_process';
1
+ import { execFile, execFileSync, spawn, spawnSync } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  import { dlopen, FFIType } from 'bun:ffi';
4
4
  import { createHash } from 'node:crypto';
@@ -10,6 +10,7 @@ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
10
10
  import { loadSelfDevRun } from '../self-dev/run-store.js';
11
11
  import { normalizeRunId } from './harness-space.js';
12
12
  import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
13
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
13
14
  import { lookupGoal, type GoalLookupOptions, type GoalLookupResult } from '../self-implement/goal-lookup.js';
14
15
  import { archiveGoals, type GoalArchiveOptions } from '../self-implement/goal-archive.js';
15
16
  import { detectBursts, formatIncidentBurstWarning, incidentLastLines, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
@@ -23,13 +24,15 @@ import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-
23
24
  import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
24
25
  import { queryRunningRuns } from '../self-implement/running-runs.js';
25
26
  import { DevPipelineError } from '../self-dev/dev-pipeline.js';
26
- import { DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
27
+ import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
28
+ import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
27
29
  import { debug } from '../debug/log.js';
28
30
  import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
29
31
  import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
30
32
  import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
31
33
  import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
32
34
  import { PR_LABELS } from '../github/pr-labels.js';
35
+ import { ghAutomationEnv } from '../autopilot/pr-manager.js';
33
36
  import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
34
37
  import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
35
38
  import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
@@ -47,6 +50,8 @@ import { runHarnessPlanRfc } from './harness-plan-rfc.js';
47
50
  import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
48
51
  import type { MissionSolveOutcome } from './mission-solve-loop.js';
49
52
  import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
53
+ import { enterJourneyNode, ensureJourneyKey, exitJourneyNode, JOURNEY_KEY_ENV, passJourneyNode, withJourneyNode } from '../self-dev/graph-journey-nodes.js';
54
+ import { intakeTakesQueue, readJourneyEdge, recordJourneyEdge } from '../self-dev/graph-journey-route.js';
50
55
 
51
56
  let harnessPlanRfcForTesting: typeof runHarnessPlanRfc | undefined;
52
57
  let harnessAskMarkerInspectorForTesting: ((ask: string) => readonly string[]) | undefined;
@@ -777,13 +782,17 @@ async function dispatchHarnessAskSay(
777
782
  immediateLaunch?: HarnessQueueDeps['launch'],
778
783
  immediateExit?: () => Promise<number>,
779
784
  ): Promise<void> {
780
- await runInjectedHarnessHandler(async () => {
785
+ // HARNESS-FULL-GRAPH — 여정 조인 키는 이 발사 동안만 환경에 싣는다(자식은 그동안 물려받는다).
786
+ const journeyPreset = Boolean(process.env[JOURNEY_KEY_ENV]?.trim());
787
+ try { await runInjectedHarnessHandler(async () => {
781
788
  assertHarnessChildLlmModel(opts);
782
789
  if (isHarnessDryRun(opts)) {
783
790
  resolveLaunchSubstrate(opts);
784
791
  printHarnessLaunchDryRun(dryRunPreview);
785
792
  return;
786
793
  }
794
+ ensureJourneyKey();
795
+ const journey = { provenance: dryRunPreview.entrance, ...(opts.goalType ? { goalType: opts.goalType as GoalType } : {}) };
787
796
  // Resolve the launch directory once; descendants and the checkpoint retain the same identity.
788
797
  const queued = process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && process.env.ELANOUS_HARNESS_SEAT;
789
798
  const queueSeat = queued === 'OP' || queued === 'TC' || queued === 'MK' || queued === 'UX' ? queued : undefined;
@@ -801,10 +810,21 @@ async function dispatchHarnessAskSay(
801
810
  const podSeat = resolved.substrate === 'pod' && ['OP', 'TC', 'MK', 'UX'].includes(inheritedSeat ?? '')
802
811
  ? inheritedSeat as QueueSeat : undefined;
803
812
  const stampedSeat = resolved.substrate === 'pod' ? opts.seat ?? queueSeat ?? podSeat ?? assigned : assigned;
813
+ // HARNESS-FULL-GRAPH 2판 — 대기열을 탈지는 «그래프»가 정한다. 코드는 상태 값(queued|direct)만 내고,
814
+ // 다음 노드는 YAML(⊕ 조건이 맞는 launch 오버레이)의 intake 간선이 고른다. 기본 YAML 은 오늘과 같은 길이다.
815
+ // ⛔ 대기열은 자리가 있어야 하고 대기열 자식·--no-queue 는 다시 줄 서지 않는다 — 그 셋은 그래프가 못 넘는다.
816
+ const intakeLabel = shouldQueue && stampedSeat ? 'queued' : 'direct';
817
+ // intake 는 판정만 하는 관측 노드 — 진입·출구를 한 자리에서.
818
+ passJourneyNode('intake', { ...journey, outcome: intakeLabel });
819
+ const intakeRoute = readJourneyEdge('intake', intakeLabel, { seat: stampedSeat ?? 'none', substrate: resolved.substrate });
820
+ const queueEligible = opts.queue !== false && !process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && Boolean(stampedSeat);
821
+ const takeQueue = intakeTakesQueue(intakeLabel, intakeRoute, queueEligible);
822
+ // 실제로 탄 간선을 전제 검사 «뒤»에 남긴다(그래프 답과 다르면 honored=false · 이유).
823
+ recordJourneyEdge(intakeRoute, takeQueue ? 'queue' : 'launch-gate', intakeRoute.to === 'queue' && !takeQueue ? (opts.queue === false ? '--no-queue' : process.env.ELANOUS_HARNESS_QUEUE_LAUNCH ? 'queue-child' : 'no-seat') : undefined);
804
824
  if (directSay && opts.queue === false) {
805
825
  try { debug.log('harness.queue', 'bypass', { seat: stampedSeat ?? null, reason: '--no-queue' }); }
806
826
  catch { /* Observation must not prevent an emergency launch. */ }
807
- } else if (shouldQueue && stampedSeat) {
827
+ } else if (takeQueue && stampedSeat) {
808
828
  resolveNestedElanousOption(opts);
809
829
  const flags: string[] = [];
810
830
  const values: Array<[string, string | undefined]> = [
@@ -833,6 +853,7 @@ async function dispatchHarnessAskSay(
833
853
  [opts.forceGate, '--force-gate'],
834
854
  ] as const) if (enabled) flags.push(flag);
835
855
  let row: QueueItem;
856
+ enterJourneyNode('queue', journey);
836
857
  try {
837
858
  row = await addHarnessQueue({ seat: stampedSeat, launchCwd: process.cwd(), refuseDuplicate: true,
838
859
  ...(dryRunPreview.goalPath ? { ask: dryRunPreview.goalPath } : { say: dryRunPreview.input }),
@@ -842,12 +863,17 @@ async function dispatchHarnessAskSay(
842
863
  }, queueDeps);
843
864
  await queueDeps?.afterEnqueue?.(row);
844
865
  } catch (error) {
845
- if (!(error instanceof HarnessQueueDuplicateError)) throw error;
866
+ if (!(error instanceof HarnessQueueDuplicateError)) {
867
+ exitJourneyNode('queue', { ...journey, outcome: 'error', data: { error: error instanceof Error ? error.message : String(error) } });
868
+ throw error;
869
+ }
846
870
  console.error(`발사 거절 — ${error.message.replace(/[\r\n]+/g, ' ')}`);
871
+ exitJourneyNode('queue', { ...journey, outcome: 'refused' });
847
872
  process.exitCode = 1;
848
873
  return;
849
874
  }
850
- const result = await tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id);
875
+ const result = await withJourneyNode('queue', journey, () => tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id));
876
+ exitJourneyNode('queue', { ...journey, outcome: result.outcome === 'launched' && result.item?.id === row.id ? 'launched' : 'waiting', data: { queueItemId: row.id, queueTick: result.outcome } });
851
877
  if (result.outcome === 'launched' && result.item?.id === row.id) {
852
878
  if (immediateExit) process.exitCode = await immediateExit();
853
879
  } else if (result.outcome === 'waiting') {
@@ -880,6 +906,8 @@ async function dispatchHarnessAskSay(
880
906
  const previous = process.env.ELANOUS_HARNESS_SEAT;
881
907
  if (stampedSeat) process.env.ELANOUS_HARNESS_SEAT = stampedSeat;
882
908
  else delete process.env.ELANOUS_HARNESS_SEAT;
909
+ // 배치 노드 — 실행 칸(local | pod)이 그 출구 라벨이고, YAML 의 decompose 간선(on: substrate)이 그 값으로 갈린다.
910
+ passJourneyNode('dispatch', { ...journey, outcome: resolved.substrate });
883
911
  try { await dispatch(resolved, stampedOpts); }
884
912
  finally {
885
913
  if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
@@ -900,11 +928,16 @@ async function dispatchHarnessAskSay(
900
928
  // CLI wiring tests time out on them. Gate tests inject deps or opt in with ELANOUS_LAUNCH_GATE_LIVE=1.
901
929
  if (process.env.NODE_ENV === 'test' && Object.keys(gate).length === 0 && process.env.ELANOUS_LAUNCH_GATE_LIVE !== '1') {
902
930
  try { debug.log('execution-loop.launch-gate', 'skipped-test-env', { goalId }); } catch { /* observation is fail-soft */ }
931
+ passJourneyNode('launch-gate', { ...journey, outcome: 'pass', data: { skipped: 'test-env' } });
903
932
  await launch();
904
933
  return;
905
934
  }
906
- const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
907
- const decision = preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
935
+ enterJourneyNode('launch-gate', journey);
936
+ const decision = await withJourneyNode('launch-gate', journey, async () => {
937
+ const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
938
+ return preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
939
+ });
940
+ exitJourneyNode('launch-gate', { ...journey, outcome: decision.action === 'proceed' ? 'pass' : 'refuse', data: { action: decision.action } });
908
941
  if (decision.action !== 'proceed') {
909
942
  console.error(`❌ launch gate: ${decision.action} — ${decision.reason.split(/\r?\n/, 1)[0]}`);
910
943
  process.exitCode = 3;
@@ -916,7 +949,7 @@ async function dispatchHarnessAskSay(
916
949
  ];
917
950
  if (warnings.length > 0) console.error(`⚠️ launch gate: ${warnings.join(' · ').split(/\r?\n/, 1)[0]}`);
918
951
  await launch();
919
- });
952
+ }); } finally { if (!journeyPreset) delete process.env[JOURNEY_KEY_ENV]; }
920
953
  }
921
954
 
922
955
  function normalizeHarnessPlanOptions(opts: HarnessPlanOptions): HarnessPlanOptions {
@@ -1559,6 +1592,28 @@ function readProcessCwd(pid: number): Pick<HarnessProcessRecord, 'cwd' | 'cwdSta
1559
1592
  }
1560
1593
  }
1561
1594
 
1595
+ /** One `lsof` for many pids (a per-pid lsof costs ~0.1–0.3 s); lsof exits 1 when some pid is gone yet prints the rest. */
1596
+ function batchProcessCwdReader(pids: readonly number[]): (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'> {
1597
+ const cwds = new Map<number, string>();
1598
+ let failure: string | undefined;
1599
+ if (pids.length) {
1600
+ const result = spawnSync('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', pids.join(',')],
1601
+ { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
1602
+ if (result.error || typeof result.stdout !== 'string') failure = observationFailureReason(result.error ?? new Error('lsof output missing'));
1603
+ else {
1604
+ let current: number | undefined;
1605
+ for (const line of result.stdout.split('\n')) {
1606
+ if (line.startsWith('p')) current = Number(line.slice(1));
1607
+ else if (line.startsWith('n') && current !== undefined && line.length > 1) cwds.set(current, line.slice(1).trim());
1608
+ }
1609
+ }
1610
+ }
1611
+ return (pid) => {
1612
+ const cwd = cwds.get(pid);
1613
+ return cwd ? { cwd, cwdStatus: 'observed' } : { cwdStatus: 'unknown', cwdFailureReason: failure ?? 'lsof cwd missing' };
1614
+ };
1615
+ }
1616
+
1562
1617
  const OWNERSHIP_ENV_KEYS = new Set<string>(Object.values(HARNESS_PROCESS_OWNERSHIP_ENV));
1563
1618
 
1564
1619
  function ownershipValueFromEntry(entry: string, key: string): string | undefined {
@@ -1714,10 +1769,23 @@ export function readProcessOwnership(
1714
1769
  }
1715
1770
  }
1716
1771
 
1717
- export function defaultListHarnessProcesses(): HarnessProcessListObservation {
1772
+ /** Narrows the per-process reads (`lsof` cwd ≈ 0.1 s each · `ps eww`) of `defaultListHarnessProcesses`. */
1773
+ export interface HarnessProcessListOptions {
1774
+ /** Keep only matching records; the others are dropped before any per-process read. */
1775
+ readonly include?: (record: HarnessProcessRecord) => boolean;
1776
+ /** Skip the ownership (`ps eww`) read — for callers that only need the cwd. */
1777
+ readonly cwdOnly?: boolean;
1778
+ /** Wall-clock budget for the per-process reads; records past it keep `cwdStatus: 'unknown'` (reason names the budget). */
1779
+ readonly deadlineMs?: number;
1780
+ readonly readCwd?: (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'>;
1781
+ readonly psOutput?: () => string;
1782
+ readonly clock?: () => number;
1783
+ }
1784
+
1785
+ export function defaultListHarnessProcesses(options: HarnessProcessListOptions = {}): HarnessProcessListObservation {
1718
1786
  let out: string;
1719
1787
  try {
1720
- out = execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
1788
+ out = options.psOutput ? options.psOutput() : execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
1721
1789
  encoding: 'utf8',
1722
1790
  timeout: 5_000,
1723
1791
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -1727,11 +1795,21 @@ export function defaultListHarnessProcesses(): HarnessProcessListObservation {
1727
1795
  }
1728
1796
  const parsed = parseHarnessProcessPsOutput(out);
1729
1797
  if (parsed.status === 'failed') return parsed;
1730
- const records = parsed.records.map((record) => ({
1731
- ...record,
1732
- ...readProcessCwd(record.pid),
1733
- ownership: readProcessOwnership(record.pid),
1734
- }));
1798
+ const clock = options.clock ?? Date.now;
1799
+ const selected = options.include ? parsed.records.filter(options.include) : parsed.records;
1800
+ const readCwd = options.readCwd ?? (options.cwdOnly ? batchProcessCwdReader(selected.map((record) => record.pid)) : readProcessCwd);
1801
+ const deadline = options.deadlineMs === undefined ? Infinity : clock() + options.deadlineMs;
1802
+ const records = selected.map((record) => {
1803
+ // A host with hundreds of harness processes made the serial reads take minutes (10-08 queue tick hang).
1804
+ if (clock() >= deadline) {
1805
+ return { ...record, cwdStatus: 'unknown' as const, cwdFailureReason: `observation deadline ${options.deadlineMs}ms exceeded` };
1806
+ }
1807
+ return {
1808
+ ...record,
1809
+ ...readCwd(record.pid),
1810
+ ...(options.cwdOnly ? {} : { ownership: readProcessOwnership(record.pid) }),
1811
+ };
1812
+ });
1735
1813
  if (parsed.status === 'incomplete') {
1736
1814
  return {
1737
1815
  status: 'incomplete',
@@ -1847,12 +1925,30 @@ type GithubPull = {
1847
1925
 
1848
1926
  type GhExecute = (args: string[]) => string;
1849
1927
 
1850
- // ⛔ `env: process.env` is required: Bun resolves the executable against the PATH it started with, not the one
1851
- // ensure-bin-path augments. Under cron's minimal PATH the hourly sweep died with «Executable not found: gh» (2026-09-28).
1852
- const executeGh: GhExecute = (args) => execFileSync('gh', args, {
1853
- encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
1854
- stdio: ['ignore', 'pipe', 'pipe'], env: process.env,
1855
- });
1928
+ // Explicit env preserves the augmented PATH under Bun (cron may start with a minimal PATH).
1929
+ // Authentication comes from the same automation policy used by the PR manager, not the user's keyring.
1930
+ // Failure details are bounded and redact the token before reaching the sweep result or debug log.
1931
+ function ghFailure(args: string[], error: unknown, token: string | undefined): Error {
1932
+ const failure = error as { status?: number | null; code?: number | string; stderr?: string | Buffer };
1933
+ const exit = failure.status ?? (typeof failure.code === 'number' ? failure.code : 'unknown');
1934
+ const stderr = String(failure.stderr ?? '');
1935
+ const stderrTail = (token ? stderr.replaceAll(token, '[REDACTED]') : stderr).slice(-300).replace(/[\r\n]+/g, ' ').trim();
1936
+ try { debug.log('harness.drafts', 'gh-failed', { args0: args[0], exit, stderrTail }); }
1937
+ catch { /* Logging must not hide the gh failure. */ }
1938
+ // gh never ran (e.g. «Executable not found: gh»): no exit or stderr to report, so keep the spawn error itself.
1939
+ if (typeof failure.status !== 'number' && !stderrTail && error instanceof Error) return error;
1940
+ return new Error(`gh ${args.slice(0, 2).join(' ')} exit=${exit}: ${stderrTail}`);
1941
+ }
1942
+
1943
+ const executeGh: GhExecute = (args) => {
1944
+ const env = ghAutomationEnv(process.env);
1945
+ try {
1946
+ return execFileSync('gh', args, {
1947
+ encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
1948
+ stdio: ['ignore', 'pipe', 'pipe'], env,
1949
+ });
1950
+ } catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
1951
+ };
1856
1952
 
1857
1953
  /** Merge-time adapter: reuse the sweep's GitHub inventory and worktree liveness without a second triage implementation. */
1858
1954
  export async function supersedeMergedGoalDrafts(
@@ -1956,9 +2052,14 @@ function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
1956
2052
  }
1957
2053
 
1958
2054
  const execFileAsync = promisify(execFile);
1959
- const executeGhAsync = async (args: string[]): Promise<string> => (await execFileAsync('gh', args, {
1960
- encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env: process.env,
1961
- })).stdout;
2055
+ const executeGhAsync = async (args: string[]): Promise<string> => {
2056
+ const env = ghAutomationEnv(process.env);
2057
+ try {
2058
+ return (await execFileAsync('gh', args, {
2059
+ encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env,
2060
+ })).stdout;
2061
+ } catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
2062
+ };
1962
2063
 
1963
2064
  /** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
1964
2065
  export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
@@ -2420,15 +2521,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2420
2521
  },
2421
2522
  getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
2422
2523
  getActiveClaimOwner: async (repository, number) => {
2524
+ // DRAFT-METRIC: the batched GraphQL comments (oldest first, proven complete) answer without a per-draft REST call.
2525
+ const batched = (await detailFor(repository, number))?.comments;
2526
+ if (batched) return activeClaimOwnerFromBodies(batched);
2423
2527
  const pages = ghJson<Array<Array<{ body: string; created_at: string }>>>(['api', '--paginate', '--slurp',
2424
2528
  `repos/${repository}/issues/${number}/comments?per_page=100`], execute);
2425
2529
  if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)
2426
2530
  || page.some((comment) => typeof comment.body !== 'string' || !Number.isFinite(Date.parse(comment.created_at)))))
2427
2531
  throw new Error('Incomplete claim comments');
2428
- const last = pages.flat().sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at))
2429
- .map((comment) => comment.body)
2430
- .find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
2431
- return last ? draftClaimOwner(last) : undefined;
2532
+ return activeClaimOwnerFromBodies(pages.flat().sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))
2533
+ .map((comment) => comment.body));
2432
2534
  },
2433
2535
  getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
2434
2536
  };
@@ -2436,12 +2538,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2436
2538
 
2437
2539
  export interface HarnessDraftSweepDeps {
2438
2540
  readonly adapters?: DraftSweepAdapters;
2541
+ /** Optional overlap source injection; tests do not read GitHub or logs.db. */
2542
+ readonly overlapAdapters?: OverlapMetricAdapters;
2439
2543
  readonly repository?: () => string;
2440
2544
  readonly write?: (line: string) => void;
2441
2545
  readonly execute?: GhExecute;
2442
2546
  readonly now?: () => Date;
2443
2547
  /** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
2444
2548
  readonly runTtl?: RunTtlAdapters;
2549
+ /** Test seam for `tools.selfImplement.draftSweepCloseCap`; omitted ⇒ the user config. */
2550
+ readonly closeCapConfig?: () => unknown;
2445
2551
  }
2446
2552
 
2447
2553
  /** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
@@ -2464,6 +2570,12 @@ async function draftSweepRunTtl(repository: string, deps: HarnessDraftSweepDeps,
2464
2570
  }
2465
2571
  }
2466
2572
 
2573
+ /** Latest claim or release among comments in chronological order (oldest first); a release means no owner. */
2574
+ export function activeClaimOwnerFromBodies(bodies: readonly string[]): string | undefined {
2575
+ const last = [...bodies].reverse().find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
2576
+ return last ? draftClaimOwner(last) : undefined;
2577
+ }
2578
+
2467
2579
  function draftClaimOwner(body: string): string | undefined {
2468
2580
  return /^🔧 처리 중 — owner ([^\n·]+?) · /m.exec(body)?.[1]?.trim();
2469
2581
  }
@@ -2520,11 +2632,43 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2520
2632
  process.exitCode = 1;
2521
2633
  }
2522
2634
  });
2635
+ drafts.command('metrics').description('draft 재고·최장 나이·needs-owner·48h 전환율 (읽기 전용 · PR 근거는 GraphQL 배치)')
2636
+ .option('--json', 'JSON 으로 출력')
2637
+ .option('--repo <owner/name>', '조회할 GitHub 저장소')
2638
+ .action(async (opts: { json?: boolean; repo?: string }) => {
2639
+ try {
2640
+ const execute = deps.execute ?? executeGh;
2641
+ const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
2642
+ if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
2643
+ const started = Date.now();
2644
+ const adapters = deps.adapters ?? githubDraftSweepAdapters(deps.execute);
2645
+ const now = deps.now?.() ?? new Date();
2646
+ const metrics = await collectDraftMetrics(repository, adapters, now);
2647
+ let overlap: Awaited<ReturnType<typeof collectOverlapMetrics>> | null = null;
2648
+ try {
2649
+ overlap = await collectOverlapMetrics(repository, deps.overlapAdapters ?? {
2650
+ ...adapters,
2651
+ listPreflightRows: readAskPreflightRowsSince,
2652
+ listSalvagedRows: readSalvagedRowsSince,
2653
+ }, now);
2654
+ } catch (error) {
2655
+ try { debug.log('self-dev.draft-metrics', 'overlap-failed', { repository, reason: sweepFailureReason(error) }); } catch { /* fail-soft */ }
2656
+ }
2657
+ try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
2658
+ const write = deps.write ?? console.log;
2659
+ if (opts.json) write(JSON.stringify({ repository, ...metrics, overlap }));
2660
+ else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
2661
+ } catch (error) {
2662
+ console.error(humanErrorLine(error));
2663
+ process.exitCode = 1;
2664
+ }
2665
+ });
2523
2666
  return drafts.command('sweep').description('draft PR 을 조사한다 (기본: 변경 없음)')
2524
2667
  .option('--apply', '판정한 라벨 변경과 종료를 적용')
2525
2668
  .option('--json', '판정 결과를 JSON 으로 출력')
2526
2669
  .option('--repo <owner/name>', '조회할 GitHub 저장소')
2527
- .action(async (opts: { apply?: boolean; json?: boolean; repo?: string }) => {
2670
+ .option('--close-cap <n>', '이번 틱 최대 종료 수 (1~100 · config tools.selfImplement.draftSweepCloseCap 보다 우선 · 기본 10)')
2671
+ .action(async (opts: { apply?: boolean; json?: boolean; repo?: string; closeCap?: string }) => {
2528
2672
  try {
2529
2673
  const write = deps.write ?? console.log;
2530
2674
  const execute = deps.execute ?? executeGh;
@@ -2537,7 +2681,16 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2537
2681
  }
2538
2682
  // One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
2539
2683
  const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
2540
- const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github! });
2684
+ const configCap = deps.closeCapConfig
2685
+ ? deps.closeCapConfig()
2686
+ : (() => { try { return getUserConfig().tools?.selfImplement?.draftSweepCloseCap; } catch { return undefined; } })();
2687
+ const cap = resolveDraftSweepCloseCap({ flag: opts.closeCap, config: configCap });
2688
+ for (const warning of cap.warnings) {
2689
+ console.error(`⚠ ${warning}`);
2690
+ try { debug.log('self-dev.draft-sweep', 'close-cap-rejected', { warning, closeCap: cap.closeCap, closeCapSource: cap.closeCapSource }); } catch { /* fail-soft */ }
2691
+ }
2692
+ const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github!,
2693
+ closeCap: cap.closeCap, closeCapSource: cap.closeCapSource });
2541
2694
  const reason = result.error ? sweepFailureReason(result.error) : undefined;
2542
2695
  const ttlStarted = Date.now();
2543
2696
  const runTtl = await draftSweepRunTtl(repository, deps, github);
@@ -2888,7 +3041,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2888
3041
  harnessQueueReceiptPath(root, item.launchId!), queueDeps.launchCommand ?? join(import.meta.dir, '../../bin/elanous.mjs'),
2889
3042
  ...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
2890
3043
  cwd: item.launchCwd ?? resolve(import.meta.dir, '../..'), stdio: 'inherit',
2891
- env: { ...process.env, ELANOUS_STATE_DIR: root, ELANOUS_HARNESS_SEAT: item.seat,
3044
+ env: { ...process.env, ELANOUS_STATE_DIR: root,
3045
+ ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
2892
3046
  ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
2893
3047
  });
2894
3048
  // Capture the end at spawn time: a child that already closed (or died by a signal) must not leave the parent waiting.
@@ -4,6 +4,7 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync
4
4
  import { join, resolve } from 'node:path';
5
5
  import { Database } from 'bun:sqlite';
6
6
  import { debug } from '../debug/log.js';
7
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
7
8
  import { findTreeRoot } from '../cli/test-flag.js';
8
9
  import { readLogInstances, type LogInstanceView } from '../mss/logging/instance-registry.js';
9
10
  import { effectiveInstanceRoot, prodInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
@@ -20,6 +21,7 @@ import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js'
20
21
  import { leaseKubectl, measurePoolLease } from '../task-orchestrator/surfaces/pod-lease.js';
21
22
  import { parsePodPool, podPoolHostLease, resolvePodPoolSpec } from '../task-orchestrator/surfaces/pod-pool.js';
22
23
  import { writeHarnessQueueReceipt } from './harness-queue-child.js';
24
+ import { adviseLaunch, compareLaunchAdvice, type LaunchAdvice } from './launch-advice.js';
23
25
  import { finishAdvice, measureFinish, recordFinishHistory, type FinishMetrics } from '../loops/orchestrator/finish-rate.js';
24
26
 
25
27
  export type QueueSeat = 'OP' | 'TC' | 'MK' | 'UX';
@@ -73,6 +75,8 @@ export interface HarnessQueueDeps {
73
75
  configPath?: string;
74
76
  cap?: (seat: QueueSeat) => number;
75
77
  pool?: () => QueuePool;
78
+ /** Shadow-only advice seam; exceptions must not affect the queue decision. */
79
+ advice?: typeof adviseLaunch;
76
80
  launch?: (item: QueueItem, args: string[], root: string) => Promise<number>;
77
81
  /** Test seam: runs after a direct CLI call enqueued its row and before that call's own tick (race tests). */
78
82
  afterEnqueue?: (item: QueueItem) => void | Promise<void>;
@@ -385,24 +389,25 @@ export function listHarnessQueue(deps: HarnessQueueDeps = {}): QueueItem[] {
385
389
  return read(harnessQueuePath(deps.root ?? effectiveInstanceRoot()));
386
390
  }
387
391
 
388
- /** A missing row is not proof of failure unless the queue left a cancellation marker. */
389
- export function harnessQueueOutcome(id: string, deps: HarnessQueueDeps = {}): 'pending' | 'succeeded' | 'retryable' | 'unknown' {
392
+ /** A missing row is not proof of failure; explicit removal and terminal receipts have distinct markers. */
393
+ export function harnessQueueOutcome(id: string, deps: HarnessQueueDeps = {}): 'pending' | 'succeeded' | 'retryable' | 'removed' | 'unknown' {
390
394
  const root = deps.root ?? effectiveInstanceRoot();
391
395
  const row = listHarnessQueue(deps).find((item) => item.id === id);
392
396
  if (!row) {
393
397
  const marker = join(root, 'harness', `${id}.outcome`);
394
398
  if (!existsSync(marker)) return 'unknown';
395
399
  const value = readFileSync(marker, 'utf8');
396
- return value === 'succeeded' || value === 'retryable' ? value : 'unknown';
400
+ return value === 'succeeded' || value === 'retryable' || value === 'removed' ? value : 'unknown';
397
401
  }
398
402
  return queueRowOutcome(row, root);
399
403
  }
400
404
 
401
405
  const keyMarkerPath = (root: string, key: string) => join(root, 'harness', 'by-key', `${createHash('sha256').update(key).digest('hex')}.id`);
402
406
 
403
- function recordQueueOutcome(root: string, id: string, outcome: 'succeeded' | 'retryable', key?: string): void {
407
+ function recordQueueOutcome(root: string, id: string, outcome: 'succeeded' | 'retryable' | 'removed', key?: string): void {
404
408
  const path = join(root, 'harness', `${id}.outcome`);
405
- if (!existsSync(path)) writeFileSync(path, outcome, { flag: 'wx', mode: 0o600 });
409
+ if (outcome === 'removed') writeFileSync(path, outcome, { mode: 0o600 });
410
+ else if (!existsSync(path)) writeFileSync(path, outcome, { flag: 'wx', mode: 0o600 });
406
411
  // The row is about to leave the queue; keep «which id last held this key» so a caller that crashed before
407
412
  // recording the id can still find the outcome (LOOP-LIVE1 · seat loop recovery).
408
413
  if (key) {
@@ -491,10 +496,8 @@ export async function removeHarnessQueue(id: string, deps: HarnessQueueDeps = {}
491
496
  if (state !== 'finished' && state !== 'not-started'
492
497
  || (deps.processes ?? readHarnessQueueProcesses)().some((row) => row.launchId === item.launchId)) return false;
493
498
  }
494
- const outcome = item.status === 'queued' ? 'retryable' : queueRowOutcome(item, root);
495
- if (item.idempotencyKey && (outcome === 'retryable' || outcome === 'succeeded')) {
496
- recordQueueOutcome(root, id, outcome, item.idempotencyKey);
497
- }
499
+ const outcome = queueRowOutcome(item, root) === 'succeeded' ? 'succeeded' : 'removed';
500
+ if (item.idempotencyKey) recordQueueOutcome(root, id, outcome, item.idempotencyKey);
498
501
  save(path, rows.filter((row) => row.id !== id));
499
502
  observe('skipped', { id, seat: item.seat, reason: 'removed' }, deps);
500
503
  return true;
@@ -699,6 +702,26 @@ export function readHarnessQueueInventory(probe: ProcessProbe = {}): QueueInvent
699
702
  if (prior && prior !== record.seat) { ledgerSeats.delete(pid); ambiguousPids.add(pid); }
700
703
  else ledgerSeats.set(pid, record.seat!);
701
704
  }
705
+ // One lsof for every candidate: a per-pid lsof (~0.1–0.3 s each) made a tick on a busy host take 10 s+ (10-08). lsof exits 1
706
+ // when some pid is gone yet still prints the others; a pid it does not print stays unattributed (charged to all seats).
707
+ let batchCwds: Map<number, string> | undefined;
708
+ const readBatchCwds = (): Map<number, string> => {
709
+ const cwds = new Map<number, string>();
710
+ const started = Date.now();
711
+ const lsof = run('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', candidates.map(({ pid }) => pid).join(',')],
712
+ { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 });
713
+ if (!lsof.error && typeof lsof.stdout === 'string') {
714
+ let current: number | undefined;
715
+ for (const line of lsof.stdout.split('\n')) {
716
+ if (line.startsWith('p')) current = Number(line.slice(1));
717
+ else if (line.startsWith('n/') && current !== undefined) cwds.set(current, line.slice(1));
718
+ }
719
+ }
720
+ try { debug.log('harness.queue', 'cwd-batch', { pids: candidates.length, read: cwds.size, ms: Date.now() - started,
721
+ ...(lsof.error ? { error: String(lsof.error).slice(0, 240) } : {}) }); }
722
+ catch { /* Observation cannot affect dispatch. */ }
723
+ return cwds;
724
+ };
702
725
  const rows: QueueProcess[] = [];
703
726
  // Attribute a run after grouping: the orchestrator may switch working trees while its launcher stays in HQ.
704
727
  for (const root of candidates.filter((row) => !byPid.has(row.ppid))) {
@@ -736,9 +759,9 @@ export function readHarnessQueueInventory(probe: ProcessProbe = {}): QueueInvent
736
759
  if (probe.cwd) cwd = probe.cwd(pid);
737
760
  else if (platform === 'linux') cwd = readlinkSync(`/proc/${pid}/cwd`);
738
761
  else {
739
- const lsof = run('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { encoding: 'utf8', timeout: 5_000 });
740
- if (lsof.error || lsof.status !== 0 || typeof lsof.stdout !== 'string') throw new Error('cwd unavailable');
741
- cwd = lsof.stdout.split('\n').find((line) => line.startsWith('n/'))?.slice(1) ?? '';
762
+ const found = (batchCwds ??= readBatchCwds()).get(pid);
763
+ if (found === undefined) throw new Error('cwd unavailable');
764
+ cwd = found;
742
765
  }
743
766
  if (!cwd.startsWith('/')) continue;
744
767
  const found = queueSeatForCwd(cwd, trees);
@@ -816,7 +839,8 @@ async function launch(item: QueueItem, args: string[], root: string, command?: s
816
839
  harnessQueueReceiptPath(root, item.launchId!), command ?? join(repo, 'bin', 'elanous.mjs'),
817
840
  ...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
818
841
  cwd: item.launchCwd ?? repo, detached: true, stdio: ['ignore', fd, fd],
819
- env: { ...process.env, ELANOUS_STATE_DIR: root, ELANOUS_HARNESS_SEAT: item.seat,
842
+ env: { ...process.env, ELANOUS_STATE_DIR: root,
843
+ ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
820
844
  ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
821
845
  });
822
846
  const pid = await new Promise<number>((done, reject) => {
@@ -1187,7 +1211,13 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
1187
1211
 
1188
1212
  async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string | undefined, followUp: boolean): Promise<QueueTick> {
1189
1213
  const root = deps.root ?? effectiveInstanceRoot();
1190
- return locked(root, async (path) => {
1214
+ const caller = 'harness-queue-tick' as const;
1215
+ let advice: LaunchAdvice = { verdict: 'unknown', why: 'pool not measured before queue decision' };
1216
+ const shadow = (pool: QueuePool | undefined): void => {
1217
+ try { advice = (deps.advice ?? adviseLaunch)({ pool, caller }); }
1218
+ catch { advice = { verdict: 'unknown', why: 'launch advice unavailable' }; }
1219
+ };
1220
+ const actual = await locked<QueueTick>(root, async (path): Promise<QueueTick> => {
1191
1221
  // A burst follow-up already ran the author shadow and the idle request in this tick's first pass.
1192
1222
  if (!followUp) try {
1193
1223
  const now = (deps.now ?? (() => new Date()))();
@@ -1196,7 +1226,11 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
1196
1226
  debug.log('loops.author-depth', 'shadow-skipped-test', {});
1197
1227
  } else {
1198
1228
  const shadow = deps.authorShadow ?? (await import('../loops/orchestrator/author-depth.js')).runAuthorDepthShadow;
1229
+ const started = Date.now();
1199
1230
  await shadow(root, now);
1231
+ // The shadow runs inside the queue lock; its duration is what other ticks wait on.
1232
+ try { debug.log('harness.queue', 'author-shadow', { ms: Date.now() - started }); }
1233
+ catch { /* Observation cannot affect dispatch. */ }
1200
1234
  }
1201
1235
  } catch (error) {
1202
1236
  try { debug.log('loops.author-depth', 'shadow-failed', { reason: String(error) }); }
@@ -1463,6 +1497,7 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
1463
1497
  if (pool && (![pool.running, pool.pending, pool.reserved, pool.limit].every((n) => Number.isSafeInteger(n) && n >= 0) || pool.limit < 1)) {
1464
1498
  poolReason = 'pod lease status incomplete';
1465
1499
  }
1500
+ shadow(poolReason ? undefined : pool);
1466
1501
  if (pool && !poolReason) {
1467
1502
  const queueLaunching = current.filter((row) => row.status === 'launching' || row.status === 'launched').length;
1468
1503
  // CHILD-UNIV-HOLD: each unreadable child universe is charged one pool slot (conservative), never a seat slot.
@@ -1545,4 +1580,13 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
1545
1580
  }
1546
1581
  return waiting();
1547
1582
  });
1583
+ // An empty queue tick has nothing to advise on; logging it would flood the advice-unknown sample.
1584
+ if (actual.outcome === 'skipped' && !actual.item) return actual;
1585
+ try {
1586
+ const { kind } = compareLaunchAdvice(advice, actual.outcome);
1587
+ debug.log('resource.advice', 'launch-advice', {
1588
+ caller, verdict: advice.verdict, why: advice.why, actual: actual.outcome, kind, itemId: actual.item?.id ?? null,
1589
+ });
1590
+ } catch { /* Shadow observation cannot affect the queue result. */ }
1591
+ return actual;
1548
1592
  }
@@ -0,0 +1,23 @@
1
+ import type { QueuePool, QueueTick } from './harness-queue.js';
2
+
3
+ export type LaunchAdvice = { verdict: 'launch-now' | 'wait' | 'unknown'; why: string; retryAfterHint?: string };
4
+ export type LaunchAdviceInput = { pool: QueuePool | null | undefined; caller: 'harness-queue-tick' | 'hand-task' | 'release-run' };
5
+
6
+ /** A pool-only suggestion; queue, seat, freeze and finish gates remain the dispatcher's decisions. */
7
+ export function adviseLaunch(input: LaunchAdviceInput): LaunchAdvice {
8
+ const pool = input.pool;
9
+ if (!pool || ![pool.running, pool.pending, pool.reserved, pool.limit].every(n => Number.isSafeInteger(n) && n >= 0)
10
+ || pool.limit < 1) return { verdict: 'unknown', why: 'pool unavailable or incomplete' };
11
+ const used = pool.running + pool.pending + pool.reserved;
12
+ if (used >= pool.limit) return { verdict: 'wait', why: `pool ${used}/${pool.limit} occupied`, retryAfterHint: 'when a pool slot is released' };
13
+ return { verdict: 'launch-now', why: `pool ${used}/${pool.limit} occupied` };
14
+ }
15
+
16
+ export function compareLaunchAdvice(advice: LaunchAdvice, actualOutcome: QueueTick['outcome']): {
17
+ agree: boolean; kind: 'advice-launch-actual-wait' | 'advice-wait-actual-launch' | 'advice-unknown' | 'agree';
18
+ } {
19
+ if (advice.verdict === 'unknown' || actualOutcome === 'skipped') return { agree: false, kind: 'advice-unknown' };
20
+ if (advice.verdict === 'launch-now' && actualOutcome === 'waiting') return { agree: false, kind: 'advice-launch-actual-wait' };
21
+ if (advice.verdict === 'wait' && actualOutcome === 'launched') return { agree: false, kind: 'advice-wait-actual-launch' };
22
+ return { agree: true, kind: 'agree' };
23
+ }