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
@@ -0,0 +1,123 @@
1
+ // GATE-IMAGE-DEPS (0.2.21 · GATE-SPEED) — the release-gate shards' dependencies come baked in the harness image.
2
+ //
3
+ // Measured on the 0.2.20 cut gate (~/.elanous/release/0.2.20/gate-logs/cut · 24 root shards · pod-<n>.log ⊕ .junit.xml):
4
+ // install-slot wait median 156 s (max 509 s) ⊕ `bun install` root+pwa median 104 s (max 160 s) = median ≈ 260 s of a
5
+ // ≈ 1200 s shard spent before the first test — and the shards that waited longest were the ones the 1200 s deadline cut
6
+ // (pod-1 · 3 · 11 · 21 · 22 waited 327–509 s and ran only 215–436 s of tests). Alone the install takes seconds; 24 at
7
+ // once thrash the node (GATE-INSTALL-CACHE). Not installing at all removes both the wait and the install.
8
+ //
9
+ // How: `docker/harness/build.sh` stages each install tree's lockfile ⊕ the install-relevant projection of its package.json
10
+ // ({@link GATE_DEPS_PROJECTION_JS}) into `gate-deps/`; the Dockerfile installs them once into {@link GATE_DEPS_DIR}
11
+ // (`--frozen-lockfile`). The layer's inputs change only when dependencies change, so the per-commit rebuild reuses it from
12
+ // the build cache. A shard links `node_modules` to the baked tree only when its own lockfile and projection are
13
+ // byte-identical to the baked ones; anything else — no baked tree (old image), a different lockfile, a different
14
+ // projection, an existing node_modules, a failed link — falls back to the old path (install slot ⊕ `bun install`).
15
+ // The link is a symlink: copying (or hard-linking across overlay layers) is the very I/O the install slots were throttling.
16
+ // Module resolution follows the real path, and the baked layout mirrors the repo (`<dir>/node_modules`,
17
+ // `<dir>/apps/pwa/node_modules`), so the parent lookup from a package inside the pwa tree is the same as before.
18
+
19
+ import { createHash } from 'node:crypto';
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+
23
+ /** Baked dependency root inside the full harness image (mirrors the repo layout). */
24
+ export const GATE_DEPS_DIR = '/opt/elanous-gate-deps';
25
+ /** The install trees, relative to the repo root, in install order (same as the old `bun install && (cd apps/pwa && bun install)`). */
26
+ export const GATE_DEPS_TREES = ['.', 'apps/pwa'] as const;
27
+
28
+ /**
29
+ * The part of a package.json that decides what `bun install` produces: dependency maps, overrides, trusted/patched
30
+ * dependencies and the install lifecycle scripts. `version`, `description` and every other script are dropped, so a
31
+ * release bump or a new script does not invalidate the baked layer. Run as `bun -e <this> <package.json>` (stdout).
32
+ * ⛔ build.sh (bake) and the shard (check) run this same text — changing it simply makes the next shards fall back once.
33
+ */
34
+ export const GATE_DEPS_PROJECTION_JS = [
35
+ "const p=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const o={};",
36
+ "for(const k of ['name','dependencies','devDependencies','optionalDependencies','peerDependencies','trustedDependencies','overrides','resolutions','patchedDependencies','workspaces'])if(p[k]!==undefined)o[k]=p[k];",
37
+ "const s={};for(const k of ['preinstall','install','postinstall','prepare'])if(p.scripts&&p.scripts[k]!==undefined)s[k]=p.scripts[k];",
38
+ "if(Object.keys(s).length)o.scripts=s;process.stdout.write(JSON.stringify(o,null,2)+'\\n');",
39
+ ].join('');
40
+
41
+ /** Same projection in-process (tests · build staging). */
42
+ export function projectDepsManifest(manifest: Record<string, unknown>): Record<string, unknown> {
43
+ const out: Record<string, unknown> = {};
44
+ for (const key of ['name', 'dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'trustedDependencies', 'overrides', 'resolutions', 'patchedDependencies', 'workspaces']) {
45
+ if (manifest[key] !== undefined) out[key] = manifest[key];
46
+ }
47
+ const scripts = manifest.scripts as Record<string, unknown> | undefined;
48
+ const lifecycle: Record<string, unknown> = {};
49
+ for (const key of ['preinstall', 'install', 'postinstall', 'prepare']) if (scripts && scripts[key] !== undefined) lifecycle[key] = scripts[key];
50
+ if (Object.keys(lifecycle).length) out.scripts = lifecycle;
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * build.sh staging: `<dest>/<tree>/bun.lock` (verbatim) ⊕ `<dest>/<tree>/package.json` (the projection) for every install
56
+ * tree. The digest (sha256 of the staged bytes, 12 hex) becomes the image label `elanous.gate-deps`.
57
+ */
58
+ export function stageGateDeps(root: string, dest: string): { digest: string; trees: string[] } {
59
+ const hash = createHash('sha256');
60
+ const trees: string[] = [];
61
+ mkdirSync(dest, { recursive: true });
62
+ for (const tree of GATE_DEPS_TREES) {
63
+ const lock = join(root, tree, 'bun.lock');
64
+ const manifest = join(root, tree, 'package.json');
65
+ if (!existsSync(lock) || !existsSync(manifest)) continue;
66
+ const out = join(dest, tree);
67
+ mkdirSync(out, { recursive: true });
68
+ const lockBytes = readFileSync(lock);
69
+ const projected = `${JSON.stringify(projectDepsManifest(JSON.parse(readFileSync(manifest, 'utf8')) as Record<string, unknown>), null, 2)}\n`;
70
+ writeFileSync(join(out, 'bun.lock'), lockBytes, { mode: 0o644 });
71
+ writeFileSync(join(out, 'package.json'), projected, { mode: 0o644 });
72
+ hash.update(`${tree}\0`).update(lockBytes).update('\0').update(projected).update('\0');
73
+ trees.push(tree);
74
+ }
75
+ return { digest: hash.digest('hex').slice(0, 12), trees };
76
+ }
77
+
78
+ const sq = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
79
+
80
+ /**
81
+ * Bash that installs the shard's dependencies (cwd = the repo clone) and sets `irc` (0 = ready). Each tree is linked to
82
+ * the baked tree when it matches, else installed — only the installs take an install slot (`install_slot_acquire` /
83
+ * `install_slot_release` from `installSlotScript` must already be defined when `installSlots > 0`). Every decision is a
84
+ * `[gate] deps <tree> baked|miss (<why>)` line in `log` (the shard's install.log).
85
+ */
86
+ export function gateDepsInstallScript(o: { log: string; installSlots: number; depsDir?: string }): string {
87
+ const deps = sq(o.depsDir ?? GATE_DEPS_DIR);
88
+ const log = o.log;
89
+ return [
90
+ 'gate_deps_link() {',
91
+ ` local t="$1" d=${deps} want`,
92
+ ' [ "$t" = . ] || d="$d/$t"',
93
+ ' if [ ! -d "$d/node_modules" ]; then echo "[gate] deps $t miss (no baked deps)"; return 1; fi',
94
+ ' if [ -e "$t/node_modules" ] || [ -L "$t/node_modules" ]; then echo "[gate] deps $t miss (node_modules exists)"; return 1; fi',
95
+ ' if ! cmp -s "$t/bun.lock" "$d/bun.lock"; then echo "[gate] deps $t miss (bun.lock differs)"; return 1; fi',
96
+ ` want=$(bun -e ${sq(GATE_DEPS_PROJECTION_JS)} "$t/package.json" 2>/dev/null) || { echo "[gate] deps $t miss (package.json unreadable)"; return 1; }`,
97
+ ' if [ "$want" != "$(cat "$d/package.json" 2>/dev/null)" ]; then echo "[gate] deps $t miss (package.json differs)"; return 1; fi',
98
+ ' if ! ln -s "$d/node_modules" "$t/node_modules"; then echo "[gate] deps $t miss (link failed)"; return 1; fi',
99
+ ' echo "[gate] deps $t baked"',
100
+ '}',
101
+ 'irc=0; gate_deps_need=',
102
+ `for t in ${GATE_DEPS_TREES.join(' ')}; do gate_deps_link "$t" >> ${log} 2>&1 || gate_deps_need="$gate_deps_need $t"; done`,
103
+ 'if [ -n "$gate_deps_need" ]; then',
104
+ ...(o.installSlots > 0 ? [` install_slot_acquire >> ${log} 2>&1`] : []),
105
+ ` for t in $gate_deps_need; do (cd "$t" && bun install) >> ${log} 2>&1; irc=$?; [ "$irc" -eq 0 ] || break; done`,
106
+ ...(o.installSlots > 0 ? [' install_slot_release'] : []),
107
+ 'fi',
108
+ ].join('\n');
109
+ }
110
+
111
+ /** Per-tree decisions from a shard's install log (`baked` · `miss:<why>`), for the gate's `pod-shard` observation. */
112
+ export function parseGateDeps(log: string): Record<string, string> {
113
+ const out: Record<string, string> = {};
114
+ for (const match of log.matchAll(/^\[gate\] deps (\S+) (baked|miss \(([^)]*)\))$/gm)) out[match[1]!] = match[2] === 'baked' ? 'baked' : `miss:${match[3]}`;
115
+ return out;
116
+ }
117
+
118
+ if (import.meta.main) {
119
+ const [cmd, dest] = process.argv.slice(2);
120
+ if (cmd !== 'stage' || !dest) { console.error('usage: pod-gate-deps.ts stage <dir>'); process.exit(2); }
121
+ const staged = stageGateDeps(process.cwd(), dest);
122
+ console.log(`digest=${staged.digest} trees=${staged.trees.join(',') || '-'}`);
123
+ }
@@ -16,13 +16,14 @@
16
16
  * 풀을 안 주면 종전처럼 «현재 컨텍스트» 하나다(동작 불변).
17
17
  */
18
18
  import { spawn, spawnSync } from 'node:child_process';
19
- import { existsSync, mkdtempSync, rmSync } from 'node:fs';
19
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, utimesSync } from 'node:fs';
20
20
  import { tmpdir } from 'node:os';
21
21
  import { join, resolve } from 'node:path';
22
22
  import { debug } from '../../debug/log.js';
23
23
  import { PodLeaseAdmission, type PodLeasePredecessor, type PodLeaseRelease } from '../../pod-lease/admission.js';
24
24
  import { predecessorState, type PredecessorState } from '../../pod-lease/dependency-state.js';
25
- import { HostPoolLease, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
25
+ import { HostPoolLease, hostLeaseBaseDir, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
26
+ import { acquireLockSync } from '../../storage/file-lock.js';
26
27
  import { harnessCpuSlotsBesideGate, measurePoolLease, recommendConcurrency, type PodLeaseMember, type PoolDnsProbe, type PoolLeaseRecommendation } from './pod-lease.js';
27
28
 
28
29
  export interface PodPoolMember {
@@ -473,13 +474,64 @@ type LocalImageInspect = (image: string) => { status: number | null; stdout: str
473
474
  /** `docker save | ssh docker load` 한 번. 시험이 실제 전송 대신 호출 횟수만 센다. */
474
475
  type ImageShip = (member: PodPoolMember, image: string) => { status: number | null; stderr: string };
475
476
 
477
+ const IMAGE_SHIP_TIMEOUT_MS = 1_800_000;
478
+ const REMOTE_RUN_TIMEOUT_MS = 900_000;
479
+
480
+ /**
481
+ * Runs a shell pipeline in its «own process group» and kills the whole group, not just bash (flood 10-08:
482
+ * 186 orphaned `docker save | gzip | ssh` pipes after runs timed out or died).
483
+ * - `perl setpgrp` gives the pipeline a group of its own, so `kill 0` cannot reach the caller.
484
+ * - The pipeline runs in the background and bash `wait`s, because bash defers a trap until a «foreground»
485
+ * command finishes — a TERM from the spawnSync timeout would otherwise wait out `docker save`.
486
+ * - A watchdog in the group polls the parent pid: if the caller dies (kill -9 included), the group goes too.
487
+ * - On a normal finish the group is swept as well (watchdog and its `sleep`).
488
+ * ⚠️ The background pipeline's stdin is /dev/null (non-interactive bash) — fine for `docker save`.
489
+ */
490
+ export function runProcessGroupPipeline(pipeline: string, opts: { timeoutMs: number; parentPid?: number; watchIntervalSec?: number }): { status: number | null; stderr: string; timedOut: boolean } {
491
+ const script = [
492
+ `trap 'trap "" TERM HUP INT; kill -TERM 0 2>/dev/null; exit 143' TERM HUP INT`,
493
+ `( while kill -0 "$ELANOUS_PIPE_PARENT" 2>/dev/null; do sleep "$ELANOUS_PIPE_WATCH"; done; kill -TERM 0 2>/dev/null ) &`,
494
+ `( set -o pipefail; eval "$ELANOUS_PIPE_CMD" ) &`,
495
+ 'wait $!',
496
+ 'rc=$?',
497
+ `trap "" TERM HUP INT`,
498
+ 'kill -TERM 0 2>/dev/null',
499
+ 'exit $rc',
500
+ ].join('\n');
501
+ const r = spawnSync('perl', ['-e', 'setpgrp(0,0) or die "setpgrp: $!"; exec @ARGV or die "exec: $!"', 'bash', '-c', script], {
502
+ encoding: 'utf8',
503
+ timeout: opts.timeoutMs,
504
+ killSignal: 'SIGTERM',
505
+ env: { ...process.env, ELANOUS_PIPE_CMD: pipeline, ELANOUS_PIPE_PARENT: String(opts.parentPid ?? process.pid), ELANOUS_PIPE_WATCH: String(opts.watchIntervalSec ?? 2) },
506
+ });
507
+ const timedOut = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
508
+ return { status: r.status, stderr: (r.stderr ?? '') + (timedOut ? ` · timed out after ${opts.timeoutMs}ms (process group killed)` : r.error ? ` · ${String(r.error)}` : ''), timedOut };
509
+ }
510
+
476
511
  function defaultImageShip(member: PodPoolMember, image: string): { status: number | null; stderr: string } {
477
- const ship = spawnSync('bash', ['-c', `set -o pipefail; docker save ${image} | gzip -1 | ssh -o BatchMode=yes -o ConnectTimeout=10 ${member.sshHost} 'export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; gunzip | docker load'`], { encoding: 'utf8', timeout: 1_800_000 });
478
- return { status: ship.status, stderr: ship.stderr ?? '' };
512
+ const ship = runProcessGroupPipeline(`docker save ${image} | gzip -1 | ssh -o BatchMode=yes -o ConnectTimeout=10 ${member.sshHost} 'export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; gunzip | docker load'`, { timeoutMs: IMAGE_SHIP_TIMEOUT_MS });
513
+ return { status: ship.status, stderr: ship.stderr };
514
+ }
515
+
516
+ /** This machine's image ID (`sha256:…` — `docker save | docker load` keeps it). Null when unreadable. */
517
+ function defaultLocalImageId(image: string): { status: number | null; stdout: string } {
518
+ const r = spawnSync('docker', ['image', 'inspect', image, '--format', '{{.Id}}'], { encoding: 'utf8', timeout: 30_000 });
519
+ return { status: r.status, stdout: r.stdout ?? '' };
520
+ }
521
+
522
+ function imageIdFrom(r: { status: number | null; stdout: string }): string | null {
523
+ const v = r.status === 0 ? r.stdout.trim() : '';
524
+ return /^sha256:[0-9a-f]{64}$/.test(v) ? v : null;
525
+ }
526
+
527
+ /** The remote node's image ID. Null when unreadable — never read as «same». */
528
+ export function remoteImageId(member: PodPoolMember, image: string, run: RemoteRun = defaultRemoteRun): string | null {
529
+ if (!member.sshHost) return null;
530
+ return imageIdFrom(run(member.sshHost, `docker image inspect ${image} --format '{{.Id}}'`));
479
531
  }
480
532
 
481
533
  export function defaultRemoteRun(host: string, script: string): { status: number | null; stdout: string; stderr: string } {
482
- const r = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host, `export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy; ${script}`], { encoding: 'utf8', timeout: 900_000 });
534
+ const r = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host, `export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy; ${script}`], { encoding: 'utf8', timeout: REMOTE_RUN_TIMEOUT_MS });
483
535
  return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? String(r.error) : '') };
484
536
  }
485
537
 
@@ -491,29 +543,100 @@ export function remoteImageCommit(member: PodPoolMember, image: string, run: Rem
491
543
  return v && v !== '<no value>' ? v : null;
492
544
  }
493
545
 
546
+ /** Cross-process lock options for one image ship per host (tests shrink them). */
547
+ export interface ImageShipLockOptions {
548
+ /** Directory for the per-host lock files. Default = the machine-wide pod lease dir (shared by every universe on this machine). */
549
+ dir?: string;
550
+ retryMs?: number;
551
+ /** A lock older than this is a crash leftover. Default = ship timeout ⊕ import timeout ⊕ 2 min. */
552
+ staleMs?: number;
553
+ maxTries?: number;
554
+ }
555
+
556
+ export function imageShipLockPath(host: string, dir: string = join(hostLeaseBaseDir(), 'image-ship')): string {
557
+ return join(dir, `${host.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 120) || 'host'}.lock`);
558
+ }
559
+
494
560
  /**
495
561
  * 원격 노드에 이 기계의 이미지를 보낸다(판이 다를 때만) — `docker save | ssh docker load` ⊕ `k3d image import`.
496
562
  * ⛔ Pod 의 elanous 는 이미지 판이다(피드백: Pod 는 main 이 아니라 이미지를 돈다) — 노드마다 판이 다르면 같은 골이 노드마다 다른 코드로 돈다.
563
+ * 🩸 10-08 flood: many runs saw the same stale label at once and all shipped together — ssh refused them
564
+ * (`Session open refused by peer` · `kex_exchange_identification`). Now:
565
+ * ① «same image» = commit label OR (label missing) the same image ID on both sides — never ship it twice.
566
+ * ② one ship per host, under a cross-process lock; whoever gets the lock re-checks and skips if another process shipped.
567
+ * ③ the ship pipeline runs in its own process group (`runProcessGroupPipeline`).
497
568
  */
498
- export function syncPoolImage(member: PodPoolMember, image: string, localCommit: string | null, run: RemoteRun | { run?: RemoteRun; inspect?: LocalImageInspect; transfer?: ImageShip } = defaultRemoteRun): { ok: boolean; action: 'local' | 'fresh' | 'built' | 'shipped' | 'failed'; detail: string } {
499
- const opts = typeof run === 'function' ? { run } : { run: run.run ?? defaultRemoteRun, inspect: run.inspect, transfer: run.transfer };
569
+ export function syncPoolImage(member: PodPoolMember, image: string, localCommit: string | null, run: RemoteRun | { run?: RemoteRun; inspect?: LocalImageInspect; inspectId?: LocalImageInspect; transfer?: ImageShip; lock?: ImageShipLockOptions } = defaultRemoteRun): { ok: boolean; action: 'local' | 'fresh' | 'built' | 'shipped' | 'failed'; detail: string } {
570
+ const opts = typeof run === 'function' ? { run } : { run: run.run ?? defaultRemoteRun, inspect: run.inspect, inspectId: run.inspectId, transfer: run.transfer, lock: run.lock };
500
571
  const remote = opts.run ?? defaultRemoteRun;
501
572
  const inspect = opts.inspect ?? defaultLocalImageInspect;
573
+ // An injected label inspect without an ID inspect = a hermetic caller: the ID check is off (no real docker call).
574
+ const inspectId = opts.inspectId ?? (opts.inspect ? null : defaultLocalImageId);
502
575
  const transfer = opts.transfer ?? defaultImageShip;
503
576
  if (!member.sshHost) return { ok: true, action: 'local', detail: 'this machine' };
577
+ const host = member.sshHost;
504
578
  const before = remoteImageCommit(member, image, remote);
505
579
  if (localCommit && before === localCommit) return { ok: true, action: 'fresh', detail: before.slice(0, 12) };
506
580
  // 통째 전송은 이 기계 이미지의 커밋 라벨이 목표와 같을 때만. 다르면 낡은 판을 보내지 않는다.
507
581
  const here = localImageCommit(image, inspect);
508
582
  if (localCommit && here !== localCommit) return { ok: false, action: 'failed', detail: '로컬 이미지 판이 다르다 · 보내지 않음' };
509
- const ship = transfer(member, image);
510
- if (ship.status !== 0) return { ok: false, action: 'failed', detail: `docker load rc=${ship.status}: ${(ship.stderr ?? '').slice(-300)}` };
511
- const imp = remote(member.sshHost, `k3d image import ${image} -c ${member.k3dCluster}`);
512
- if (imp.status !== 0) return { ok: false, action: 'failed', detail: `k3d import rc=${imp.status}: ${imp.stderr.slice(-300)}` };
513
- const after = remoteImageCommit(member, image, remote);
514
- return after && (!localCommit || after === localCommit)
515
- ? { ok: true, action: 'shipped', detail: `${before?.slice(0, 12) ?? '없음'} → ${after.slice(0, 12)}` }
516
- : { ok: false, action: 'failed', detail: `보낸 뒤 판이 ${after ?? '없음'} — 기대 ${localCommit ?? '?'}` };
583
+ const localId = inspectId ? imageIdFrom(inspectId(image)) : null;
584
+ /** Fresh = the commit label matches, or the very same image (ID) is already there. Null = not fresh. */
585
+ const freshDetail = (label: string | null): string | null => {
586
+ if (localCommit && label === localCommit) return label.slice(0, 12);
587
+ if (localId && remoteImageId(member, image, remote) === localId) return `same image id ${localId.slice(7, 19)}`;
588
+ return null;
589
+ };
590
+ const sameId = freshDetail(before);
591
+ if (sameId) {
592
+ debug.log('pod.pool', 'image-ship-skipped-same-id', { host, image, localCommit, remoteCommit: before, detail: sameId });
593
+ return { ok: true, action: 'fresh', detail: sameId };
594
+ }
595
+
596
+ const lockOpts = opts.lock ?? {};
597
+ const retryMs = lockOpts.retryMs ?? 2_000;
598
+ const staleMs = lockOpts.staleMs ?? IMAGE_SHIP_TIMEOUT_MS + REMOTE_RUN_TIMEOUT_MS + 120_000;
599
+ const lockDir = lockOpts.dir ?? join(hostLeaseBaseDir(), 'image-ship');
600
+ const lockPath = imageShipLockPath(host, lockDir);
601
+ const t0 = Date.now();
602
+ let waited = false;
603
+ let release: () => void;
604
+ try {
605
+ mkdirSync(lockDir, { recursive: true });
606
+ release = acquireLockSync(lockPath, {
607
+ staleMs,
608
+ retryBusyMs: retryMs,
609
+ maxTries: lockOpts.maxTries ?? Math.ceil(staleMs / retryMs) + 10,
610
+ breakDeadHolder: true,
611
+ onWait: (holder) => { waited = true; debug.log('pod.pool', 'image-ship-lock-wait', { host, image, lockPath, holder }); },
612
+ });
613
+ } catch (error) {
614
+ const reason = error instanceof Error ? error.message : String(error);
615
+ debug.log('pod.pool', 'image-ship-lock-failed', { host, image, lockPath, reason }, { level: 'warn' });
616
+ return { ok: false, action: 'failed', detail: `image ship lock: ${reason}` };
617
+ }
618
+ try {
619
+ debug.log('pod.pool', 'image-ship-lock-acquired', { host, image, lockPath, waited, waitedMs: Date.now() - t0 });
620
+ // ⭐ Re-check after the lock: the holder we waited for may have shipped this very image.
621
+ const labelNow = waited ? remoteImageCommit(member, image, remote) : before;
622
+ const fresh = waited ? freshDetail(labelNow) : null;
623
+ if (fresh) {
624
+ debug.log('pod.pool', 'image-ship-skipped-fresh-after-wait', { host, image, localCommit, remoteCommit: labelNow, detail: fresh, waitedMs: Date.now() - t0 });
625
+ return { ok: true, action: 'fresh', detail: `${fresh} (shipped by another process while waiting)` };
626
+ }
627
+ const ship = transfer(member, image);
628
+ if (ship.status !== 0) return { ok: false, action: 'failed', detail: `docker load rc=${ship.status}: ${(ship.stderr ?? '').slice(-300)}` };
629
+ // The import phase gets its own stale window (the lock file's age restarts here).
630
+ try { const now = new Date(); utimesSync(lockPath, now, now); } catch { /* lock gone · finish anyway */ }
631
+ const imp = remote(host, `k3d image import ${image} -c ${member.k3dCluster}`);
632
+ if (imp.status !== 0) return { ok: false, action: 'failed', detail: `k3d import rc=${imp.status}: ${imp.stderr.slice(-300)}` };
633
+ const after = remoteImageCommit(member, image, remote);
634
+ return after && (!localCommit || after === localCommit)
635
+ ? { ok: true, action: 'shipped', detail: `${labelNow?.slice(0, 12) ?? '없음'} → ${after.slice(0, 12)}` }
636
+ : { ok: false, action: 'failed', detail: `보낸 뒤 판이 ${after ?? '없음'} — 기대 ${localCommit ?? '?'}` };
637
+ } finally {
638
+ release();
639
+ }
517
640
  }
518
641
 
519
642
  export type PoolKubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
@@ -59,6 +59,8 @@ import { homedir } from 'node:os';
59
59
  import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
60
60
  import { findGitDir } from '../../git-fs/locate.js';
61
61
  import { debug } from '../../debug/log.js';
62
+ import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-dev/graph-journey-nodes.js';
63
+ import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
62
64
  import { selectLiveDetail } from '../../live/detail-switch.js';
63
65
  import { authStorePath } from '../../oauth/store.js';
64
66
  import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
@@ -1066,9 +1068,27 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1066
1068
  : { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before a pool slot opened' } };
1067
1069
  const unfitDone = await unfitFailure();
1068
1070
  if (unfitDone) return unfitDone;
1069
- if (!await acquireAdmission()) return admissionFailure();
1070
- let member: PodPoolMember | null = await acquireSlot();
1071
- if (options.pool && !member) return { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before a pool slot opened' } };
1071
+ // HARNESS-FULL-GRAPH — Pod 입장(입장 관문 ⊕ 멤버 슬롯)은 그래프 노드 pool-admit 이다.
1072
+ // 2판: decompose →(substrate=pod) pool-admit 간선은 «여기»(Pod 입장 직전 · 실제 단계 경계)에서 남긴다.
1073
+ routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
1074
+ enterJourneyNode('pool-admit', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId } });
1075
+ // ⛔ 감싸는 함수(async 래퍼)를 쓰지 않는다 — 마이크로태스크가 늘면 Job apply 순서가 밀린다(시험이 잡았다). 같은 자리 try/catch 로.
1076
+ const podAdmitError = (error: unknown): void => {
1077
+ exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'error', data: { spaceId: input.spaceId, error: error instanceof Error ? error.message : String(error) } });
1078
+ };
1079
+ let admitted: boolean;
1080
+ try { admitted = await acquireAdmission(); } catch (error) { podAdmitError(error); throw error; }
1081
+ if (!admitted) {
1082
+ exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'refused', data: { spaceId: input.spaceId, reason: 'admission' } });
1083
+ return admissionFailure();
1084
+ }
1085
+ let member: PodPoolMember | null;
1086
+ try { member = await acquireSlot(); } catch (error) { podAdmitError(error); throw error; }
1087
+ if (options.pool && !member) {
1088
+ exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'refused', data: { spaceId: input.spaceId, reason: 'no-slot' } });
1089
+ return { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before a pool slot opened' } };
1090
+ }
1091
+ exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'admitted', data: { spaceId: input.spaceId, ...(member ? { context: member.context } : {}) } });
1072
1092
  const currentContext = member ? null : baseKubectl(['config', 'current-context']);
1073
1093
  let context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
1074
1094
  const kubectl: Kubectl = (args, stdin) => baseKubectl(['--context', context, ...args], stdin);
@@ -1135,10 +1155,17 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1135
1155
  };
1136
1156
  const candidates = grok ? [] : plannedCodexAccounts ?? [plannedAccount];
1137
1157
  const usable: Array<{ name: string; credential: ReturnType<typeof accountCredentials> }> = [];
1138
- for (const candidate of candidates) {
1139
- const credential = await podCodexCredentialWithPrerefresh(candidate, prerefreshDeps);
1140
- if (credential) usable.push({ name: candidate, credential });
1158
+ enterJourneyNode('credential-refresh', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId, candidates: candidates.length } });
1159
+ try {
1160
+ for (const candidate of candidates) {
1161
+ const credential = await podCodexCredentialWithPrerefresh(candidate, prerefreshDeps);
1162
+ if (credential) usable.push({ name: candidate, credential });
1163
+ }
1164
+ } catch (error) {
1165
+ exitJourneyNode('credential-refresh', { provenance: 'self-implement-pod', outcome: 'error', data: { spaceId: input.spaceId, error: error instanceof Error ? error.message : String(error) } });
1166
+ throw error;
1141
1167
  }
1168
+ exitJourneyNode('credential-refresh', { provenance: 'self-implement-pod', outcome: grok || usable.length > 0 ? 'usable' : 'exhausted', data: { spaceId: input.spaceId, usable: usable.length } });
1142
1169
  if (!grok && usable.length === 0) throw new Error(`openai-codex 후보(${candidates.join(', ')}) access token 이 전부 3시간 안에 만료 — 선갱신도 못 했다(본부 임대 없음 또는 갱신 실패 · pod.credential-prerefresh 를 보라)`);
1143
1170
  const account = grok ? plannedAccount : usable[0]!.name;
1144
1171
  if (account !== plannedAccount) debug.log('self-implement.pod', 'account-skipped-expiring', { spaceId: input.spaceId, from: plannedAccount, to: account });
@@ -1725,6 +1752,12 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1725
1752
  // SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
1726
1753
  const podReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
1727
1754
  if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
1755
+ if (podExitCode === 1) {
1756
+ // HARNESS-FULL-GRAPH — 실패 쪽 두 노드: 결과 해석(result-parse) → 수확(salvage, Pod Job 이 push 한 줄로 판정).
1757
+ passJourneyNode('result-parse', { provenance: 'self-implement-pod', outcome: podReason ? 'parsed' : 'no-reason', data: { job: name } });
1758
+ const salvageOutcome = /^ELANOUS_POD_SALVAGE \S+ \S+$/m.test(logs) ? 'pushed' : logs.includes('ELANOUS_POD_SALVAGE_NONE') ? 'none' : 'unknown';
1759
+ passJourneyNode('salvage', { provenance: 'self-implement-pod', outcome: salvageOutcome, data: { job: name } });
1760
+ }
1728
1761
  return {
1729
1762
  exitCode: podExitCode,
1730
1763
  output: tail,
@@ -55,6 +55,8 @@ export interface SelfImplementDisposition {
55
55
  prNumber?: number;
56
56
  /** Head SHA checked by the Pod before host regate. */
57
57
  checkedHeadCommit?: string;
58
+ /** Review verdict only when the child bound a real pass to a clean, unchanged worktree head. */
59
+ selfReview?: { verdict: 'pass'; head: string };
58
60
  hostRegate?: { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string };
59
61
  merged?: boolean;
60
62
  detail?: string;
@@ -469,6 +471,11 @@ export function parseSelfImplementJson(stdout: string): SelfImplementDisposition
469
471
  ...(typeof o.prUrl === 'string' ? { prUrl: o.prUrl } : {}),
470
472
  ...(typeof o.prNumber === 'number' ? { prNumber: o.prNumber } : {}),
471
473
  ...(typeof o.checkedHeadCommit === 'string' ? { checkedHeadCommit: o.checkedHeadCommit } : {}),
474
+ ...(o.reviewedHeadCommit && typeof o.reviewedHeadCommit === 'string' && /^[0-9a-f]{40}$/i.test(o.reviewedHeadCommit)
475
+ && o.review && typeof o.review === 'object' && !Array.isArray(o.review)
476
+ && (o.review as { reviewed?: unknown; verdict?: unknown }).reviewed === true
477
+ && (o.review as { verdict?: unknown }).verdict === 'pass'
478
+ ? { selfReview: { verdict: 'pass' as const, head: o.reviewedHeadCommit } } : {}),
472
479
  ...(typeof o.hostRegate === 'object' && o.hostRegate !== null ? { hostRegate: o.hostRegate as SelfImplementDisposition['hostRegate'] } : {}),
473
480
  ...(typeof o.merged === 'boolean' ? { merged: o.merged } : {}),
474
481
  ...(typeof o.detail === 'string' ? { detail: o.detail } : {}),
@@ -56,6 +56,8 @@ import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
56
56
  import { CardStore } from './task-cards/card-store.js';
57
57
  import type { ContextNowDeps } from './context-bus/context-now.js';
58
58
  import type { RunningRunsResult } from './self-implement/running-runs.js';
59
+ import { checklistBoardSlash } from './release-loop/checklist-board.js';
60
+ import type { Checklist } from './release-loop/checklist.js';
59
61
 
60
62
  /** How many prior user/assistant turns to pass into `executeSkill` as
61
63
  * the `## Recent conversation` block. Mirrors the dashboard default
@@ -348,6 +350,7 @@ export function defaultTelegramCommands(
348
350
  coreCommands: readonly SlashCommand[] = SLASH_COMMANDS,
349
351
  maturity: { tuiSlash: Readonly<Record<string, Maturity>>; telegramCommand: Readonly<Record<string, Maturity>>; discordCommand: Readonly<Record<string, Maturity>> } = FEATURE_MATURITY,
350
352
  runningRunsQuery?: () => RunningRunsResult,
353
+ checklistRead?: (version: string) => Checklist,
351
354
  ): TgSlashCommand[] {
352
355
  const handled: TgSlashCommand[] = [
353
356
  {
@@ -376,6 +379,11 @@ export function defaultTelegramCommands(
376
379
  description: 'Running harness runs — same output as TUI /harness runs',
377
380
  handler: async (args) => telegramRunsSlash(args, runningRunsQuery),
378
381
  },
382
+ {
383
+ name: 'board',
384
+ description: '판 체크리스트 — /board [판]',
385
+ handler: async (args) => checklistBoardSlash(args, checklistRead),
386
+ },
379
387
  {
380
388
  name: 'release',
381
389
  description: '발행 현황 — 지금 런·노드·막힘을 한 통으로',
@@ -121,6 +121,12 @@ export function attachTelegramProjectButtons(
121
121
  await bot.answerCallbackQuery(q.id, { text: '개인 대화에서만 고를 수 있습니다' });
122
122
  return;
123
123
  }
124
+ // Reject callbacks outside Telegram's 64-byte limit and empty project ids.
125
+ if (Buffer.byteLength(q.data, 'utf8') > 64 || q.data.length === 4) {
126
+ debug.log('telegram.project', 'button-rejected', { reason: 'invalid-callback-data', chatId: q.chatId });
127
+ await bot.answerCallbackQuery(q.id, { text: '프로젝트를 다시 골라 주세요' });
128
+ return;
129
+ }
124
130
  const chatId = q.chatId;
125
131
  const session = deps.findSession(chatId, q.threadId, bot.botId);
126
132
  if (!session) {
package/src/telegram.ts CHANGED
@@ -81,7 +81,7 @@ import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-follow
81
81
  import { attachTelegramProjectButtons } from './telegram-project-command.js';
82
82
  import { attachTelegramFabricPlan } from './telegram-fabric-plan.js';
83
83
  import { contextNow as readContextNow, type ContextNowDeps } from './context-bus/context-now.js';
84
- import { renderTelegramNow } from './context-bus/context-now-surfaces.js';
84
+ import { renderContextFirstNow } from './context-bus/context-now-surfaces.js';
85
85
  import { createContextFirstGate, renderContextFirst } from './context-bus/context-first.js';
86
86
 
87
87
  export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
@@ -437,7 +437,7 @@ export class TelegramBot {
437
437
  const cfg = this.slashContext?.userConfig.telegram;
438
438
  if (cfg?.contextFirst === false) return;
439
439
  try {
440
- await this.sendMessage(ctx.chatId, renderContextFirst(this.readContextNow, this.contextNowDeps, renderTelegramNow), { replyTo: ctx.messageId, threadId: ctx.threadId });
440
+ await this.sendMessage(ctx.chatId, renderContextFirst(this.readContextNow, this.contextNowDeps, renderContextFirstNow), { replyTo: ctx.messageId, threadId: ctx.threadId });
441
441
  } catch (err) {
442
442
  this.log(`telegram context-first failed: ${err instanceof Error ? err.message : String(err)}`);
443
443
  }
@@ -16,6 +16,12 @@
16
16
  // D1). It holds a lazy singleton per elanous process: first call
17
17
  // spawns Chrome, subsequent calls reuse; the client lives until the
18
18
  // process exits or `closeBrowserRuntime()` is invoked.
19
+ // BrowserRead remains read-only. An explicitly requested BrowserNavigate
20
+ // input asks a person through the HITL confirmation channels only and fails
21
+ // closed without approval. The shell-command approver is never consulted: an
22
+ // unattended surface may auto-allow shell patterns, which must not type into a page.
23
+ // Click still runs through performBrowserAction in src/harness/browser-act.ts,
24
+ // where armed, reversibility and host-boundary checks precede CdpClient.click.
19
25
 
20
26
  import type { CdpClient } from '../browser-cdp/client.js';
21
27
  import { createCdpClient, CdpUnavailable } from '../browser-cdp/client.js';
@@ -24,6 +30,9 @@ import { defaultControlSignalBus } from '../input/control-signal.js';
24
30
  import type { LLMToolSpec } from '../llm.js';
25
31
  import { debug } from '../debug/log.js';
26
32
  import { getHarnessRunId } from '../harness/harness-space.js';
33
+ import { decideTypeAction } from '../harness/browser-act-type.js';
34
+ import { requestConfirmation } from '../hitl/confirm.js';
35
+ import type { TypeTarget } from '../harness/bot-type-request.js';
27
36
  import type { ToolRuntime, ToolRuntimeContext } from './types.js';
28
37
 
29
38
  // ─── Dependency injection ───────────────────────────────────────
@@ -116,6 +125,8 @@ export async function closeBrowserRuntime(): Promise<void> {
116
125
  export interface BrowserNavigateArgs {
117
126
  /** Absolute URL. Scheme required. */
118
127
  url: string;
128
+ /** Input requires a selected target and explicit approval; no form submission. */
129
+ input?: { selector: string; text: string };
119
130
  /** When true, block until Page.loadEventFired (or timeoutMs).
120
131
  * Default true. */
121
132
  waitForLoad?: boolean;
@@ -147,6 +158,16 @@ export function buildBrowserNavigateTool(): LLMToolSpec {
147
158
  type: 'string',
148
159
  description: 'Absolute URL including scheme (https://...). about:blank also accepted.',
149
160
  },
161
+ input: {
162
+ type: 'object',
163
+ description: 'Type into a selected page field only after explicit approval. Never submits a form.',
164
+ properties: {
165
+ selector: { type: 'string', description: 'CSS selector of the input field.' },
166
+ text: { type: 'string', description: 'Text without newlines (at most 200 characters).' },
167
+ },
168
+ required: ['selector', 'text'],
169
+ additionalProperties: false,
170
+ },
150
171
  waitForLoad: {
151
172
  type: 'boolean',
152
173
  description: 'Wait for Page.loadEventFired before returning. Default true.',
@@ -165,9 +186,33 @@ export function buildBrowserNavigateTool(): LLMToolSpec {
165
186
  const DEFAULT_NAV_TIMEOUT_MS = 10_000;
166
187
  const MAX_NAV_TIMEOUT_MS = 60_000;
167
188
 
168
- export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promise<BrowserNavigateResult> {
189
+ export async function dispatchBrowserNavigate(args: BrowserNavigateArgs, ctx?: ToolRuntimeContext): Promise<BrowserNavigateResult> {
169
190
  ensureBrowserControlSignalConsumer();
170
191
  const url = String(args.url ?? '').trim();
192
+ const input = args.input;
193
+ // Input is never an implicit side effect of navigation. Approval is scoped
194
+ // to this URL, selector and text, before connecting or dispatching CDP.
195
+ if (input !== undefined) {
196
+ if (typeof input.selector !== 'string' || !input.selector.trim()
197
+ || typeof input.text !== 'string' || !input.text.trim()
198
+ || /[\r\n]/.test(input.text) || input.text.length > 200) {
199
+ return { output: '# BrowserNavigate: invalid input', finalUrl: '', title: '', loadMs: 0 };
200
+ }
201
+ let httpUrl = false;
202
+ try { httpUrl = ['http:', 'https:'].includes(new URL(url).protocol); } catch { /* invalid URL */ }
203
+ if (!httpUrl) return { output: '# BrowserNavigate: invalid input URL', finalUrl: '', title: '', loadMs: 0 };
204
+ let approved = false;
205
+ try {
206
+ // Human confirmation only — `ctx.approver` is the shell-command approver (TC review of #24990).
207
+ approved = (await requestConfirmation({
208
+ prompt: `Browser input on ${url}`,
209
+ detail: `Selector: ${input.selector}\nText: ${input.text}`,
210
+ ...(ctx?.confirmChannels ? { channels: ctx.confirmChannels } : {}),
211
+ onTimeout: () => false,
212
+ })).answer === true;
213
+ } catch { /* approval failure is denial */ }
214
+ if (!approved) return { output: '# BrowserNavigate: input not approved', finalUrl: '', title: '', loadMs: 0 };
215
+ }
171
216
  if (!url) {
172
217
  observeBrowserAction({ action: 'navigate', url: '', target: 'url', ok: false, error: 'url is required' });
173
218
  return { output: '# BrowserNavigate: error\nurl is required', finalUrl: '', title: '', loadMs: 0 };
@@ -233,6 +278,45 @@ export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promis
233
278
  timedOut = r.timedOut;
234
279
  }
235
280
 
281
+ if (input !== undefined) {
282
+ try {
283
+ if (!client.input) throw new Error('CDP input unavailable');
284
+ const currentUrl = await client.evaluate('document.location.href');
285
+ if (currentUrl !== new URL(url).href) throw new Error('CDP input refused: page URL changed after approval');
286
+ const inspected = await client.evaluate(`(() => {
287
+ const element = document.querySelector(${JSON.stringify(input.selector)});
288
+ if (!(element instanceof HTMLElement)) return null;
289
+ return { tag: element.tagName.toLowerCase(),
290
+ type: element instanceof HTMLInputElement ? element.type : null,
291
+ name: element.getAttribute('name'), id: element.getAttribute('id'),
292
+ contentEditable: element.isContentEditable, inForm: !!element.closest('form') };
293
+ })()`);
294
+ const candidate = inspected && typeof inspected === 'object' ? inspected as Record<string, unknown> : null;
295
+ const target: TypeTarget | null = candidate
296
+ && typeof candidate.tag === 'string'
297
+ && (candidate.type === null || typeof candidate.type === 'string')
298
+ && (candidate.name === null || typeof candidate.name === 'string')
299
+ && (candidate.id === null || typeof candidate.id === 'string')
300
+ && typeof candidate.contentEditable === 'boolean' && typeof candidate.inForm === 'boolean'
301
+ ? candidate as TypeTarget : null;
302
+ const verdict = decideTypeAction({ url, selector: input.selector, text: input.text,
303
+ // The caller's approval is for this exact URL; it cannot authorize a different host.
304
+ actionHosts: [new URL(url).hostname], armed: true, target });
305
+ if (!verdict.allowed) throw new Error(verdict.reason);
306
+ const focused = await client.evaluate(`(() => {
307
+ const element = document.querySelector(${JSON.stringify(input.selector)});
308
+ if (!(element instanceof HTMLElement)) return false;
309
+ element.focus();
310
+ return document.activeElement === element;
311
+ })()`);
312
+ if (focused !== true) throw new Error('CDP input target could not be focused');
313
+ await client.input(input.text);
314
+ } catch (err) {
315
+ return { output: `# BrowserNavigate: input error\n${err instanceof Error ? err.message : String(err)}`,
316
+ finalUrl: url, title: '', loadMs: Date.now() - started };
317
+ }
318
+ }
319
+
236
320
  const loadMs = Date.now() - started;
237
321
 
238
322
  // Best-effort metadata. Errors here don't abort the tool.
@@ -255,8 +339,8 @@ export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promis
255
339
  export const browserNavigateRuntime: ToolRuntime<BrowserNavigateArgs, BrowserNavigateResult> = {
256
340
  id: 'browser_navigate',
257
341
  spec: buildBrowserNavigateTool(),
258
- async run(req, _ctx: ToolRuntimeContext) {
259
- return dispatchBrowserNavigate(req);
342
+ async run(req, ctx: ToolRuntimeContext) {
343
+ return dispatchBrowserNavigate(req, ctx);
260
344
  },
261
345
  };
262
346
 
@@ -177,6 +177,7 @@ export { setWorktreeRuntimeDeps } from './git-worktree-runtimes.js';
177
177
  export {
178
178
  setSelfImplementApprover,
179
179
  setSelfImplementRuntimeDeps,
180
+ setSelfImplementSoftStopSpaceObserver,
180
181
  } from '../self-implement/self-implement-runtime.js';
181
182
  export {
182
183
  setTerminalSessionsGetter,