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,315 @@
1
+ /**
2
+ * HARNESS-PARENT-ON-MSB1 — `tasks hand --live` 의 하니스 «부모»를 다른 호스트(ssh)에서 띄우는 첫 조각 ⊕ 낡은 부모 보고.
3
+ *
4
+ * 왜 «첫 조각»인가 (2026-10-08 실측):
5
+ * - 부모가 호스트에서 기대는 것이 전부 «호스트 로컬 파일»이다 — 착지 동결(`landing-freeze.json` ⊕ `landing-merges/` 표지
6
+ * 무용), 풀 예약(`HostPoolLease`), 런 원장(`run-ledger/`), logs.db. 원격 부모가 `--merge-by-host` 로 병합하면 HQ 의
7
+ * 동결·컷 배수(drain)를 «못 본 채» 병합한다.
8
+ * - 그래서 원격 부모는 «PR 까지만» 간다: `--merge-by-host` → `--no-auto-merge`. 병합 권한은 HQ 에 남는다(동결 안전).
9
+ * - 발사 전 점검(preflight)이 하나라도 막히면 원격으로 띄우지 않고 «종전 로컬 발사»로 돌아간다(fail-closed · 관측 남김).
10
+ * - 관측: 카드에 `parentHost` 를 적고, `tasks show` 가 원격 런 원장을 ssh 로 HQ 의 거울 디렉터리로 당겨 읽는다.
11
+ *
12
+ * 기본 OFF — `taskAgent.parentHost` 가 없고 `ELANOUS_TA_PARENT_HOST` 도 없으면 아무것도 바뀌지 않는다.
13
+ */
14
+ import { spawnSync } from 'node:child_process';
15
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { join, resolve } from 'node:path';
17
+ import { debug } from '../debug/log.js';
18
+ import { shellQuote } from './task-hand.js';
19
+
20
+ /** `taskAgent.parentHost` 설정 칸. */
21
+ export interface ParentHostConfig {
22
+ /** ssh 대상(예: `node-b`). */
23
+ host: string;
24
+ /** 원격에서 부모를 띄울 git 작업 트리(절대 경로 · 원격 기준). */
25
+ cwd: string;
26
+ /** 원격 elanous 진입점 — 기본 `$HOME/.local/share/elanous/bin/elanous`(설치본). */
27
+ elanous?: string;
28
+ /** 원격 config-dir — 기본 `$HOME/.elanous`. */
29
+ configDir?: string;
30
+ /** 원격 부모에 줄 `--pod-pool`(원격 kube context 이름이 HQ 와 다를 때). */
31
+ podPool?: string;
32
+ }
33
+
34
+ export const PARENT_HOST_ENV = 'ELANOUS_TA_PARENT_HOST';
35
+ const DEFAULT_REMOTE_ELANOUS = '$HOME/.local/share/elanous/bin/elanous';
36
+ const DEFAULT_REMOTE_CONFIG_DIR = '$HOME/.elanous';
37
+ const HOST_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]{0,127}$/;
38
+
39
+ /**
40
+ * 켜졌나 — env 가 이긴다(`off`·`0`·빈 값이면 끔). env 가 호스트를 주면 나머지 칸(cwd 등)은 설정에서 받는다.
41
+ * cwd 가 없으면 켜지지 않는다(어디서 띄울지 모르는 원격 발사는 하지 않는다).
42
+ */
43
+ export function resolveParentHost(config: Partial<ParentHostConfig> | undefined, env: NodeJS.ProcessEnv = process.env): ParentHostConfig | null {
44
+ const fromEnv = env[PARENT_HOST_ENV];
45
+ if (fromEnv !== undefined && ['', '0', 'off', 'false', 'local'].includes(fromEnv.trim().toLowerCase())) return null;
46
+ const host = (fromEnv?.trim() || config?.host || '').trim();
47
+ if (!host || !HOST_RE.test(host)) return null;
48
+ const cwd = config?.cwd?.trim();
49
+ if (!cwd || !cwd.startsWith('/')) return null;
50
+ return { host, cwd, ...(config?.elanous ? { elanous: config.elanous } : {}), ...(config?.configDir ? { configDir: config.configDir } : {}), ...(config?.podPool ? { podPool: config.podPool } : {}) };
51
+ }
52
+
53
+ /** `$HOME/…` 은 원격 셸이 풀도록 따옴표 밖에 둔다 — 나머지는 shellQuote. */
54
+ function remotePath(path: string): string {
55
+ return path.startsWith('$HOME/') ? `"$HOME"/${shellQuote(path.slice('$HOME/'.length))}` : shellQuote(path);
56
+ }
57
+
58
+ /** 원격 부모 인자 — HQ 병합 권한을 넘기지 않는다: `--merge-by-host` → `--no-auto-merge` ⊕ (있으면) `--pod-pool`. */
59
+ export function remoteParentArgs(args: readonly string[], podPool?: string): string[] {
60
+ const out = args.map((arg) => arg === '--merge-by-host' ? '--no-auto-merge' : arg);
61
+ if (!out.includes('--no-auto-merge') && out[0] === 'harness') out.splice(2, 0, '--no-auto-merge');
62
+ if (podPool && !out.includes('--pod-pool')) out.splice(2, 0, '--pod-pool', podPool);
63
+ return out;
64
+ }
65
+
66
+ /** 원격 로그 파일(원격 기준) — 부모 stdout/stderr. */
67
+ export function remoteParentLogPath(target: ParentHostConfig, runId: string): string {
68
+ return `${target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR}/remote-parents/${runId}.log`;
69
+ }
70
+
71
+ /** ssh 로 넘길 원격 셸 한 줄 — 떼어 띄우고 pid 만 찍는다. */
72
+ export function remoteLaunchScript(target: ParentHostConfig, args: readonly string[], env: Record<string, string>): string {
73
+ const elanous = target.elanous ?? DEFAULT_REMOTE_ELANOUS;
74
+ const configDir = target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR;
75
+ const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
76
+ const envPart = Object.entries(env).filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`).join(' ');
77
+ const argv = [remotePath(elanous), '--config-dir', remotePath(configDir), ...remoteParentArgs(args, target.podPool).map(shellQuote)].join(' ');
78
+ // 준비가 실패하면 «확실히 안 띄웠다»(not-launched=…)를 찍고 끝낸다 — 그때만 호출부가 로컬로 돌아간다.
79
+ // 띄운 뒤에는 짧게 살아 있는지 보고 pid 를 찍는다(조기 종료면 early-exit — 역시 «안 돈다»가 확실).
80
+ return `mkdir -p ${remotePath(`${configDir}/remote-parents`)} || { echo "not-launched=mkdir"; exit 3; }; `
81
+ + `cd ${shellQuote(target.cwd)} || { echo "not-launched=cwd"; exit 3; }; `
82
+ + `${envPart ? `env ${envPart} ` : ''}nohup ${argv} >> ${remotePath(log)} 2>&1 < /dev/null & p=$!; sleep 2; `
83
+ + `if kill -0 "$p" 2>/dev/null; then echo "pid=$p"; else echo "not-launched=early-exit"; tail -3 ${remotePath(log)}; exit 4; fi`;
84
+ }
85
+
86
+ /** ssh 실행기(시험 주입). */
87
+ export type SshRunner = (host: string, script: string, timeoutMs: number) => { status: number | null; stdout: string; stderr: string };
88
+
89
+ export const defaultSshRunner: SshRunner = (host, script, timeoutMs) => {
90
+ const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=8', '-o', 'ControlPath=none', host, script], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
91
+ return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? (result.error ? String(result.error) : '') };
92
+ };
93
+
94
+ /** 발사 전 점검 결과 — `gaps` 가 비어야 원격으로 띄운다. */
95
+ export interface ParentHostPreflight { ok: boolean; gaps: string[]; facts: Record<string, string> }
96
+
97
+ /** 원격 사실 한 번에 — key=value 줄. */
98
+ export function preflightScript(target: ParentHostConfig): string {
99
+ const elanous = remotePath(target.elanous ?? DEFAULT_REMOTE_ELANOUS);
100
+ const configDir = remotePath(target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR);
101
+ return [
102
+ `echo "version=$(${elanous} --version 2>/dev/null | tail -1 | cut -d' ' -f1)"`,
103
+ `test -f ${configDir}/config.json && echo config=1`,
104
+ `grep -Eq '"podPool"[[:space:]]*:[[:space:]]*"[^"[:space:]]+' ${configDir}/config.json 2>/dev/null && echo podPool=1`,
105
+ `(cd ${shellQuote(target.cwd)} 2>/dev/null && git rev-parse --is-inside-work-tree >/dev/null 2>&1) && echo repo=1`,
106
+ `command -v kubectl >/dev/null 2>&1 && echo kubectl=1`,
107
+ `true`,
108
+ ].join('; ');
109
+ }
110
+
111
+ export interface PreflightInput {
112
+ target: ParentHostConfig;
113
+ /** HQ 의 elanous 판(첫 낱말). */
114
+ localVersion: string;
115
+ /** HQ 착지 동결이 «발사도 막는» 상태인가 — 원격 부모는 그 동결을 못 본다. */
116
+ launchHoldActive: boolean;
117
+ ssh?: SshRunner;
118
+ }
119
+
120
+ /** 순수 판정 — 원격 사실 → 막는 칸 목록. */
121
+ export function judgeParentHostFacts(facts: Record<string, string>, input: Omit<PreflightInput, 'ssh' | 'target'> & { podPoolOverride?: boolean }): string[] {
122
+ const gaps: string[] = [];
123
+ if (facts.unreachable) gaps.push(`unreachable: ${facts.unreachable}`);
124
+ else {
125
+ if (!facts.version) gaps.push('remote-elanous-missing');
126
+ else if (facts.version !== input.localVersion) gaps.push(`version-mismatch: remote ${facts.version} ≠ HQ ${input.localVersion}`);
127
+ if (facts.config !== '1') gaps.push('remote-config-missing');
128
+ if (facts.podPool !== '1' && !input.podPoolOverride) gaps.push('remote-pod-pool-unset');
129
+ if (facts.repo !== '1') gaps.push('remote-repo-missing');
130
+ if (facts.kubectl !== '1') gaps.push('remote-kubectl-missing');
131
+ }
132
+ if (input.launchHoldActive) gaps.push('hq-launch-freeze-active');
133
+ return gaps;
134
+ }
135
+
136
+ export function preflightParentHost(input: PreflightInput): ParentHostPreflight {
137
+ const ssh = input.ssh ?? defaultSshRunner;
138
+ const facts: Record<string, string> = {};
139
+ const result = ssh(input.target.host, preflightScript(input.target), 20_000);
140
+ if (result.status !== 0) facts.unreachable = (result.stderr.trim().split('\n').pop() || `rc=${result.status}`).slice(0, 200);
141
+ else for (const line of result.stdout.split('\n')) {
142
+ const at = line.indexOf('=');
143
+ if (at > 0) facts[line.slice(0, at).trim()] = line.slice(at + 1).trim();
144
+ }
145
+ const gaps = judgeParentHostFacts(facts, { localVersion: input.localVersion, launchHoldActive: input.launchHoldActive, podPoolOverride: Boolean(input.target.podPool) });
146
+ return { ok: gaps.length === 0, gaps, facts };
147
+ }
148
+
149
+ /** 원격이 «확실히 안 띄웠다»고 답했다 — 호출부가 로컬로 돌아가도 중복 발사가 아니다. */
150
+ export class RemoteParentNotLaunchedError extends Error {
151
+ constructor(host: string, reason: string) { super(`원격 부모 안 뜸(${host}): ${reason}`); this.name = 'RemoteParentNotLaunchedError'; }
152
+ }
153
+
154
+ /**
155
+ * 원격 발사 — pid 를 받으면 성공. 원격이 `not-launched=…` 로 답하면 `RemoteParentNotLaunchedError`(로컬로 가도 된다).
156
+ * 그 밖(ssh 끊김·시간 초과·알 수 없는 답)은 «떴는지 모른다» — 일반 Error 로 던져 카드가 launch-failed 로 남게 한다
157
+ * (⛔ 로컬로 또 띄우면 원격에 이미 뜬 부모와 중복이 될 수 있다).
158
+ */
159
+ export function launchRemoteParent(target: ParentHostConfig, args: readonly string[], env: Record<string, string>, ssh: SshRunner = defaultSshRunner): { pid: number; log: string } {
160
+ const result = ssh(target.host, remoteLaunchScript(target, args, env), 30_000);
161
+ const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
162
+ const pid = Number(/^pid=(\d+)$/m.exec(result.stdout)?.[1]);
163
+ if (result.status === 0 && Number.isSafeInteger(pid) && pid > 0) return { pid, log };
164
+ const refused = /^not-launched=(\S+)/m.exec(result.stdout)?.[1];
165
+ if (refused && (result.status === 3 || result.status === 4)) {
166
+ const tail = result.stdout.split('\n').filter((line) => line.trim() && !line.startsWith('not-launched=')).slice(-1)[0] ?? '';
167
+ throw new RemoteParentNotLaunchedError(target.host, `${refused}${tail ? `: ${tail.slice(0, 160)}` : ''}`);
168
+ }
169
+ throw new Error(`원격 부모 발사 결과 불확실(${target.host} · rc=${result.status}) — 중복을 피해 로컬로 다시 띄우지 않는다 · 원격에서 ELANOUS_RUN_ID=${env.ELANOUS_RUN_ID ?? '?'} 를 확인: ${(result.stderr.trim().split('\n').pop() || '').slice(0, 160)}`);
170
+ }
171
+
172
+ // ── 관측: 원격 런 원장 거울 ─────────────────────────────────────────────────────────────
173
+
174
+ const RUN_ID_RE = /^run-[A-Za-z0-9_-]{4,64}$/;
175
+
176
+ /** HQ 쪽 거울 디렉터리 — `<prodRoot>/remote-parents/<host>/run-ledger`. */
177
+ export function remoteLedgerMirrorDir(prodRoot: string, host: string): string {
178
+ return join(prodRoot, 'remote-parents', host.replace(/[^A-Za-z0-9._-]/g, '_'), 'run-ledger');
179
+ }
180
+
181
+ /** 거울 디렉터리 전부(카드 원장 읽기가 연합 목록 뒤에 붙인다). */
182
+ export function remoteLedgerMirrorDirs(prodRoot: string): string[] {
183
+ const base = join(prodRoot, 'remote-parents');
184
+ if (!existsSync(base)) return [];
185
+ try {
186
+ return readdirSync(base).map((host) => join(base, host, 'run-ledger')).filter((dir) => existsSync(dir));
187
+ } catch { return []; }
188
+ }
189
+
190
+ /**
191
+ * 원격 런 원장(부모 ⊕ 그 원장이 가리키는 Pod 자식)을 HQ 거울로 당긴다 — 읽기 전용(원격에 쓰지 않는다).
192
+ * 돌려주는 값 = 당긴 런 id. 원격을 못 읽으면 던진다(호출부가 «없음»이 아니라 실패로 적는다).
193
+ */
194
+ export function mirrorRemoteRunLedgers(target: Pick<ParentHostConfig, 'host' | 'configDir'>, runId: string, prodRoot: string, ssh: SshRunner = defaultSshRunner): string[] {
195
+ if (!RUN_ID_RE.test(runId)) throw new Error(`invalid runId: ${runId}`);
196
+ const dir = remoteLedgerMirrorDir(prodRoot, target.host);
197
+ mkdirSync(dir, { recursive: true });
198
+ const remoteDir = `${target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR}/run-ledger`;
199
+ const pulled: string[] = [];
200
+ const pull = (id: string): string | null => {
201
+ const file = remotePath(`${remoteDir}/${id}.jsonl`);
202
+ // 부재만 ABSENT — 권한·읽기 오류는 cat 의 비0 종료로 올라와 던진다(«없음»으로 바꾸지 않는다).
203
+ const result = ssh(target.host, `if [ -e ${file} ]; then cat ${file}; else echo __ELANOUS_ABSENT__; fi`, 20_000);
204
+ if (result.status !== 0) throw new Error(`원격 원장 읽기 실패(${target.host} ${id}): ${(result.stderr.trim().split('\n').pop() || `rc=${result.status}`).slice(0, 200)}`);
205
+ const path = join(dir, `${id}.jsonl`);
206
+ if (result.stdout.trim() === '__ELANOUS_ABSENT__') {
207
+ // 원격에서 사라졌으면 낡은 거울도 걷는다 — 지난 사진을 지금 근거로 읽지 않게.
208
+ rmSync(path, { force: true });
209
+ return null;
210
+ }
211
+ const tmp = `${path}.${process.pid}.tmp`;
212
+ writeFileSync(tmp, result.stdout);
213
+ renameSync(tmp, path);
214
+ pulled.push(id);
215
+ return result.stdout;
216
+ };
217
+ const host = pull(runId);
218
+ if (host) {
219
+ const children = new Set<string>();
220
+ for (const line of host.split('\n')) {
221
+ try {
222
+ const entry = JSON.parse(line) as { event?: string; data?: { childRunId?: unknown } };
223
+ if (entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string' && RUN_ID_RE.test(entry.data.childRunId) && entry.data.childRunId !== runId) children.add(entry.data.childRunId);
224
+ } catch { /* 깨진 줄은 원장 읽기가 다룬다 */ }
225
+ }
226
+ for (const child of children) pull(child);
227
+ }
228
+ try { debug.log('task-agent', 'remote-ledger-mirrored', { host: target.host, runId, pulled }); } catch { /* fail-soft */ }
229
+ return pulled;
230
+ }
231
+
232
+ // ── 낡은 부모 보고(읽기 전용 · 죽이지 않는다) ─────────────────────────────────────────────
233
+
234
+ export interface HarnessParentProcess { pid: number; ageHours: number; runId: string | null; version: string | null; seat: string | null; kind: 'say' | 'ask' | 'queue-child' }
235
+
236
+ /** macOS/Linux `etime` — `[[dd-]hh:]mm:ss`. */
237
+ export function parseEtime(value: string): number | null {
238
+ const match = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(value.trim());
239
+ if (!match) return null;
240
+ const [, d, h, m, s] = match;
241
+ return Number(d ?? 0) * 86400 + Number(h ?? 0) * 3600 + Number(m) * 60 + Number(s);
242
+ }
243
+
244
+ /** `ps -axwwE -o pid=,etime=,command=` 출력 → 하니스 부모들. 런 id 는 env 꼬리의 «마지막» `ELANOUS_RUN_ID=`. */
245
+ export function parseHarnessParents(psOutput: string): HarnessParentProcess[] {
246
+ const rows: HarnessParentProcess[] = [];
247
+ for (const line of psOutput.split('\n')) {
248
+ const match = /^\s*(\d+)\s+(\S+)\s+(.*)$/.exec(line);
249
+ if (!match) continue;
250
+ const command = match[3]!;
251
+ const head = command.slice(0, 600);
252
+ const kind = /harness-queue-child\.ts/.test(head) ? 'queue-child' : /elanous\.mjs\b.*\bharness (say|ask)\b/.exec(head)?.[1] as 'say' | 'ask' | undefined;
253
+ if (!kind) continue;
254
+ const seconds = parseEtime(match[2]!);
255
+ if (seconds === null) continue;
256
+ const runIds = [...command.matchAll(/(?:^|\s)ELANOUS_RUN_ID=(run-[A-Za-z0-9_-]{4,64})(?=\s|$)/g)].map((m) => m[1]!);
257
+ rows.push({
258
+ pid: Number(match[1]),
259
+ ageHours: Math.round((seconds / 3600) * 10) / 10,
260
+ runId: runIds.at(-1) ?? null,
261
+ version: /\/versions\/([^/\s]+)\//.exec(head)?.[1] ?? null,
262
+ seat: /--seat (\S+)/.exec(head)?.[1] ?? null,
263
+ kind,
264
+ });
265
+ }
266
+ return rows;
267
+ }
268
+
269
+ export interface StaleParentRow extends HarnessParentProcess { reasons: string[]; runStatus: string | null }
270
+
271
+ export interface StaleParentDeps {
272
+ ps?: () => string;
273
+ /** 런 원장(없으면 null · 못 읽으면 던진다). */
274
+ loadLedger?: (runId: string) => Array<{ event: string; data?: Record<string, unknown> }> | null;
275
+ /** 살아 있는 Pod Job 의 `elanous.run`/`elanous.child-run` 라벨 — null 이면 «못 쟀다»(Pod 근거 없음). */
276
+ podRuns?: () => ReadonlySet<string> | null;
277
+ labelOf?: (runId: string) => string;
278
+ }
279
+
280
+ /**
281
+ * 나이 ≥ olderThanHours 인 부모 중 «원장이 끝났다»(run-status 줄) 또는 «Pod 이 없다»(Pod 목록을 쟀고 부모·자식 라벨이 없다)인 것.
282
+ * 읽기만 한다 — 죽이지 않는다. 런 id 를 모르는 부모는 `run-id-unknown` 으로 따로 적는다(낡음 판정이 아니다).
283
+ */
284
+ export function staleHarnessParents(olderThanHours: number, deps: StaleParentDeps = {}): { rows: StaleParentRow[]; scanned: number; podRunsMeasured: boolean } {
285
+ const ps = deps.ps ?? (() => spawnSync('ps', ['-axwwE', '-o', 'pid=,etime=,command='], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).stdout ?? '');
286
+ const parents = parseHarnessParents(ps());
287
+ let podRuns: ReadonlySet<string> | null = null;
288
+ try { podRuns = deps.podRuns ? deps.podRuns() : null; } catch { podRuns = null; }
289
+ const label = deps.labelOf ?? ((id: string) => id);
290
+ const rows: StaleParentRow[] = [];
291
+ for (const parent of parents) {
292
+ if (parent.ageHours < olderThanHours) continue;
293
+ const reasons: string[] = [];
294
+ let runStatus: string | null = null;
295
+ if (!parent.runId) reasons.push('run-id-unknown');
296
+ else {
297
+ let ledger: ReturnType<NonNullable<StaleParentDeps['loadLedger']>> = null;
298
+ try { ledger = deps.loadLedger ? deps.loadLedger(parent.runId) : null; } catch (error) { reasons.push(`ledger-unreadable: ${(error instanceof Error ? error.message : String(error)).slice(0, 80)}`); }
299
+ const status = [...(ledger ?? [])].reverse().find((entry) => entry.event === 'run-status' && typeof entry.data?.runStatus === 'string');
300
+ if (status) { runStatus = String(status.data!.runStatus); reasons.push(`ledger-terminal(${runStatus})`); }
301
+ if (podRuns) {
302
+ const ids = [parent.runId, ...(ledger ?? []).filter((entry) => entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string').map((entry) => entry.data!.childRunId as string)];
303
+ if (!ids.some((id) => podRuns!.has(label(id)))) reasons.push('pod-gone');
304
+ }
305
+ }
306
+ if (reasons.length) rows.push({ ...parent, reasons, runStatus });
307
+ }
308
+ rows.sort((a, b) => b.ageHours - a.ageHours);
309
+ return { rows, scanned: parents.length, podRunsMeasured: podRuns !== null };
310
+ }
311
+
312
+ /** HQ 의 elanous 판 — 이 패키지의 package.json(첫 낱말 = `--version` 첫 낱말과 같은 모양). */
313
+ export function localElanousVersion(): string {
314
+ try { return String((JSON.parse(readFileSync(resolve(import.meta.dir, '../../package.json'), 'utf8')) as { version?: string }).version ?? ''); } catch { return ''; }
315
+ }
@@ -16,12 +16,16 @@ import { readTaskAgentState, readTaskCard, taskAgentStatePath, type CompletionKi
16
16
  import type { SupervisorJobResult, SupervisorStopReason } from '../self-dev/run-supervisor.js';
17
17
  import { executeNextAction, type DeliveryKind, type NextAction } from './actions.js';
18
18
  import { judgeNextMove, type TaskJudgeInput, type TaskJudgement } from './judge.js';
19
- import { configuredTaskAgentLiveMoves, executeLiveGreenProposal, executeLiveReview, type LiveMoveDeps, type LiveMoveResult, type TaskAgentLiveMove } from './live-moves.js';
19
+ import { configuredTaskAgentLiveMoves, executeLiveGreenProposal, executeLiveLand, executeLiveReview, type LiveMoveDeps, type LiveMoveResult, type TaskAgentLiveMove } from './live-moves.js';
20
20
 
21
21
  export interface TaskAgentShadowInput {
22
22
  runId: string | null;
23
23
  stopReason: SupervisorStopReason;
24
24
  results: readonly SupervisorJobResult[];
25
+ /** One judgement cycle can attempt at most one land, even across PRs. */
26
+ cycleId?: string;
27
+ /** Reviewed SHA is mandatory for land; a bare pass cannot authorize merging. */
28
+ selfReview?: { verdict: 'pass' | 'fail'; head: string };
25
29
  }
26
30
 
27
31
  export interface TaskAgentShadowMove {
@@ -47,7 +51,7 @@ export interface TaskAgentShadowDeps {
47
51
  readCard?: (taskId: string) => TaskCard | undefined;
48
52
  /** Explicit live invocation only; supervisor's default hook always uses shadow. */
49
53
  mode?: 'shadow' | 'live';
50
- /** TA-JUDGE-LIVE-SAFE — 실제로 할 안전한 수(`review`·`propose-green`). 비면 설정 `taskAgent.liveMoves` 를 읽는다. */
54
+ /** TA-JUDGE-LIVE-SAFE — 실제로 할 수(`review`·`propose-green`·`propose-land`). 없으면 설정 `taskAgent.liveMoves` 를 읽는다. */
51
55
  liveMoves?: ReadonlySet<TaskAgentLiveMove>;
52
56
  /** live 수의 부작용(리뷰 띄우기·PR 머리 조회·상태 파일) — 시험은 주입한다. */
53
57
  live?: LiveMoveDeps;
@@ -161,6 +165,7 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
161
165
  }
162
166
  const kind = card?.completion;
163
167
  const judgeInput = shadowJudgeInput(input.stopReason, input.results, kind);
168
+ if (input.selfReview) judgeInput.review = input.selfReview.verdict;
164
169
  const delivery = successful?.worktreePath && card?.project?.target && isDeliveryKind(kind)
165
170
  ? { kind, worktreePath: successful.worktreePath, projectTarget: card.project.target, files: selectDeliveryCandidates(successful.worktreePath, kind) }
166
171
  : undefined;
@@ -209,8 +214,8 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
209
214
  move.reason = wouldDo ?? judgement.reason;
210
215
  move.wouldDo = null;
211
216
  }
212
- const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' ? move.move : null;
213
- // 판단마다 해석한다 — 안전하지 않은 수를 판단할 때도 모르는 항목 경고가 남게(실행은 아래 두 수뿐).
217
+ const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' || move.move === 'propose-land' ? move.move : null;
218
+ // 판단마다 해석한다 — 허용되지 않은 수를 판단할 때도 모르는 항목 경고가 남게.
214
219
  const liveMoves = deps.liveMoves ?? await configuredTaskAgentLiveMoves();
215
220
  const live = liveKind !== null && liveMoves.has(liveKind);
216
221
  // 카드 id 는 모든 판단 관측에 싣는다(그림자 일치율을 카드로 잇는다) — 읽기 전용 조회.
@@ -237,6 +242,16 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
237
242
  ...(prResult.prUrl ? { prUrl: prResult.prUrl } : {}),
238
243
  } } : {}),
239
244
  }, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
245
+ } else if (liveKind === 'propose-land') {
246
+ liveMove = await executeLiveLand(boundCard, prResult?.prNumber,
247
+ { runId: input.runId, cycleId: input.cycleId, review: input.selfReview,
248
+ ...(prResult?.worktreePath ? { cwd: prResult.worktreePath } : {}),
249
+ ...(prResult && !prResult.worktreePath ? { produced: {
250
+ ...(prResult.checkedHeadCommit !== undefined ? { headCommit: prResult.checkedHeadCommit } : {}),
251
+ ...(prResult.branch ? { branch: prResult.branch } : {}),
252
+ ...(prResult.prUrl ? { prUrl: prResult.prUrl } : {}),
253
+ } } : {}),
254
+ }, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
240
255
  } else {
241
256
  const evidence: Record<string, string> = {};
242
257
  if (boundCard && !delivery && judgeInput.prState === 'MERGED' && prResult) evidence[boundCard.id] = `#${prResult.prNumber}`;
@@ -77,10 +77,14 @@ export interface TaskCard {
77
77
  launchId?: string;
78
78
  /** Pod 런이면 PR 을 연 자식 런 id(런 묶기가 PR 과 함께 적는다). */
79
79
  runChildId?: string;
80
+ /** HARNESS-PARENT-ON-MSB1 — 부모가 도는 원격 호스트(없으면 HQ 로컬). 원격 부모는 PR 까지만 간다(병합은 HQ). */
81
+ parentHost?: { host: string; configDir?: string; pid?: number; log?: string };
80
82
  /** 미션 카드만 — 모든 조각 착지 → 칸 green «제안»(체크리스트는 손대지 않는다 · OP/주인이 뒤집는다). */
81
83
  greenProposal?: { at: string; checklistId: string | null; evidence: Record<string, string> };
82
84
  /** TA-JUDGE-LIVE-SAFE — live `review` 수가 리뷰를 요청한 PR 머리들(머리마다 한 번 · 띄우기 실패면 error · 이력은 지우지 않는다). */
83
85
  reviewRequests?: Array<{ pr: number; head: string; at: string; error?: string }>;
86
+ /** Live land attempts, claimed before invocation, once per PR head even if the command fails. */
87
+ landAttempts?: Array<{ pr: number; head: string; at: string; ok?: boolean; detail?: string }>;
84
88
  }
85
89
 
86
90
  /** 런 원장에서 묶은 PR — url 은 원장(pr-opened · Pod job-finished)에 있을 때만(지어내지 않는다). */
@@ -193,7 +197,11 @@ export function shellQuote(arg: string): string {
193
197
  /** 발사기에 넘기는 연결 — env(`ELANOUS_RUN_ID`)를 자식 환경에 얹으면 하니스 런이 `runId` 로 돈다(`resolveRunIdentity` 상속). launchId 는 카드에만 남는 발사 토큰이다. */
194
198
  export interface TaskLaunchContext { runId: string; launchId: string; env: Record<string, string> }
195
199
  /** 발사기가 «이 런 id 로 띄웠다»고 돌려주는 영수증 — 없으면 카드에 런 id 를 적지 않는다(추측하지 않는다). */
196
- export interface TaskLaunchReceipt { runId?: string }
200
+ export interface TaskLaunchReceipt {
201
+ runId?: string;
202
+ /** HARNESS-PARENT-ON-MSB1 — 부모를 원격 호스트에서 띄웠다(ssh). 카드에 적어 `tasks show` 가 그 호스트의 원장을 당긴다. */
203
+ parentHost?: { host: string; configDir?: string; pid?: number; log?: string };
204
+ }
197
205
  export type TaskLauncher = (args: string[], cwd?: string, context?: TaskLaunchContext) => void | TaskLaunchReceipt | Promise<void | TaskLaunchReceipt>;
198
206
 
199
207
  export interface HandTaskOptions {
@@ -328,8 +336,9 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
328
336
  const launched: TaskCard = {
329
337
  ...card, status: 'launched',
330
338
  ...(boundRunId ? { runId: boundRunId, launchId } : {}),
339
+ ...(receipt && receipt.parentHost && typeof receipt.parentHost.host === 'string' ? { parentHost: receipt.parentHost } : {}),
331
340
  history: [
332
- { at: launchedAt, event: 'launch', detail: move.command!.join(' ') },
341
+ { at: launchedAt, event: 'launch', detail: `${receipt && receipt.parentHost ? `[parent@${receipt.parentHost.host} · PR 까지만] ` : ''}${move.command!.join(' ')}` },
333
342
  ...(boundRunId ? [{ at: launchedAt, event: 'run-bound', detail: boundRunId, runId: boundRunId }] : []),
334
343
  ],
335
344
  };
@@ -13,6 +13,7 @@ import { getUserConfig } from '../../user-config.js';
13
13
  import { debug } from '../../debug/log.js';
14
14
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
15
15
  import { collectPodArtifacts } from './pod-artifact-return.js';
16
+ import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
16
17
  import { podBunCacheVolume } from './pod-bun-cache.js';
17
18
  import { podHostDirsInit, podInstallSlotsVolume } from './pod-install-slots.js';
18
19
  import { type PodPoolMember, PodPoolScheduler, resolvePodPoolSpec, parsePodPool, checkPodPool, syncPoolImages, type PoolKubectl } from './pod-pool.js';
@@ -136,6 +137,19 @@ for i in $(seq 1 60); do
136
137
  done
137
138
  echo "[gate] ISOLATION NOT ENFORCED within 30s"; exit 1`;
138
139
 
140
+ /** Export the command's log store into the existing outbox return channel without changing the command exit status. */
141
+ const COMMAND_LOG_EXPORT = `if mkdir -p "$HOME/outbox/pod-logs" && elanous --test="$HOME/.elanous-test" logs --instance prod --since 12h --limit 20000 --json --json-data > "$HOME/outbox/pod-logs/logs.jsonl"; then
142
+ logs_size=$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))
143
+ if [ "$logs_size" -gt 5242880 ]; then
144
+ tail -c 5242880 "$HOME/outbox/pod-logs/logs.jsonl" | tail -n +2 > "$HOME/outbox/pod-logs/logs.jsonl.tail" && mv -f "$HOME/outbox/pod-logs/logs.jsonl.tail" "$HOME/outbox/pod-logs/logs.jsonl"
145
+ printf 'ELANOUS_POD_LOGS_TRUNCATED %s %s\\n' "$logs_size" "$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))"
146
+ fi
147
+ else
148
+ logs_rc=$?
149
+ rm -f "$HOME/outbox/pod-logs/logs.jsonl"
150
+ printf 'ELANOUS_POD_LOGS_UNAVAILABLE export-exit-%s\\n' "$logs_rc"
151
+ fi`;
152
+
139
153
  /** 기존 Job 과 같은 `~/outbox` 조각 형식(`ELANOUS_POD_ARTIFACT`). 원장 회수는 하지 않는다. */
140
154
  const ARTIFACT_EMIT = `set -o pipefail
141
155
  artifact_bytes=0
@@ -172,6 +186,8 @@ export interface PodCommandJobInput {
172
186
  repoUrl: string;
173
187
  command: readonly string[];
174
188
  skills: readonly string[];
189
+ /** pod run opt-in: return this command's local log store with its outbox artifacts. */
190
+ returnLogs?: boolean;
175
191
  llm?: 'grok';
176
192
  /** 저장소를 clone 한다 — 비공개 저장소라 GitHub 토큰이 Secret 으로 간다(명시 opt-in). 기본은 clone 없이 `~/work` 에서 이미지의 `elanous` 로 돈다. */
177
193
  clone?: boolean;
@@ -222,6 +238,8 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
222
238
  `set -- ${quotedArgs}`,
223
239
  '"$@"',
224
240
  'rc=$?',
241
+ // A command may not end in LF; keep artifact markers on their own line for the existing parser.
242
+ ...(o.returnLogs ? ["printf '\\n'", COMMAND_LOG_EXPORT] : []),
225
243
  ARTIFACT_EMIT,
226
244
  'exit $rc',
227
245
  ].join('\n');
@@ -325,6 +343,8 @@ export interface PodCommandResult {
325
343
 
326
344
  export interface RunPodCommandOptions {
327
345
  command: readonly string[];
346
+ /** pod run opt-in; other runPodCommand callers keep their existing Job script. */
347
+ returnLogs?: boolean;
328
348
  pool?: string;
329
349
  skills?: readonly string[];
330
350
  llm?: 'grok';
@@ -357,6 +377,8 @@ export interface RunPodCommandOptions {
357
377
  readSkillEnv?: (skills: readonly string[]) => Record<string, string>;
358
378
  grokCredentials?: () => { grokAuth?: string; grokApiKey?: string; ghToken: string };
359
379
  artifactsRoot?: string;
380
+ /** Test seam for the launching instance's log store. */
381
+ logStore?: LogStore | null;
360
382
  sleep?: (ms: number) => Promise<void>;
361
383
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
362
384
  env?: NodeJS.ProcessEnv;
@@ -454,7 +476,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
454
476
  const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
455
477
  const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
456
478
  const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
457
- const artifactsDir = `${options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`}/${name}`;
479
+ const artifactRoot = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
480
+ const artifactsDir = `${artifactRoot}/${name}`;
458
481
  try {
459
482
  if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
460
483
  let grokAuth: string | undefined;
@@ -499,7 +522,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
499
522
  const manifest = podCommandJobManifest({
500
523
  name, namespace, launch, image: imageRef ?? image,
501
524
  ...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
502
- repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
525
+ repoUrl, command, skills, ...(options.returnLogs ? { returnLogs: true } : {}), ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
503
526
  ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(installSlots ? { installSlots } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
504
527
  ...(options.runId ? { runId: options.runId } : {}),
505
528
  });
@@ -519,19 +542,31 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
519
542
  const logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
520
543
  const logsFailed = logsResult.status !== 0;
521
544
  const logs = logsFailed ? '' : logsResult.stdout;
522
- const artifactDir = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
545
+ const artifactDir = artifactRoot;
523
546
  let artifacts: PodArtifactCollection;
524
547
  if (logsFailed) {
525
548
  const reason = logsResult.stderr.trim() || `kubectl logs exit ${logsResult.status}`;
526
549
  artifacts = { files: null, names: [], error: reason };
527
550
  log('pod.command', 'artifact-collect-failed', { job: name, reason });
528
551
  } else {
552
+ const store = options.returnLogs ? (options.logStore === undefined ? getDefaultLogStore() : options.logStore) : null;
529
553
  collectPodArtifacts(logs, {
530
- dir: artifactDir,
531
- job: name,
532
- log: (c, e, d) => log(c, e, d),
554
+ dir: artifactDir, job: name, log: (c, e, d) => log(c, e, d),
555
+ ...(options.returnLogs ? { logStore: store } : {}),
533
556
  });
534
557
  const names = artifactNames(logs);
558
+ // A plain command need not write JSONL. Make its child output queryable without changing the store schema.
559
+ if (options.returnLogs && store) {
560
+ try {
561
+ const output = logs.split(/\r?\n/).filter((line) => !line.startsWith('ELANOUS_POD_ARTIFACT ')).join('\n').trim();
562
+ if (output) store.insertPodBatch(name, [{ line: -1, surface: 'pod', rec: {
563
+ ts: new Date().toISOString(), category: 'pod.command', event: 'child-output', level: 'info',
564
+ data: { job: name, output: tailBytes(output, options.childLogMaxBytes ?? podChildLogMaxBytes()), origin: 'pod', podJob: name },
565
+ } }]);
566
+ } catch (error) {
567
+ log('pod.command', 'child-output-import-failed', { job: name, reason: error instanceof Error ? error.message : String(error) });
568
+ }
569
+ }
535
570
  artifacts = { files: names.length, names, error: null };
536
571
  }
537
572
  // 표지가 없어도 알리는 경로는 실제로 존재해야 한다. 자식 로그 전문은 child.log (상한 넘으면 끝부분).