elanous 0.2.24 → 0.2.27

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 (601) 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/{Q-7iofoxOEv1WX18S35of → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1027.a0f04663d24c2779.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1038.676007ae505822e7.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1113.a37eadf7f740d955.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/112-fa7c526c20ce1405.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1144.5fa1fa1d48dbcd12.js +36 -0
  9. package/apps/pwa/out/_next/static/chunks/1146.80d15ea7c6e6ee95.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1157.f08bf2b70915e176.js +148 -0
  11. package/apps/pwa/out/_next/static/chunks/119b405e.8fdd0e44d1ff37aa.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/1296.04560d98e73fbf51.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1314-f604ba013ec535b0.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1454-a9c338f5bb556f42.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1464.f3209af0d8ae4ff2.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/{8532.a86e99acf9b761cb.js → 1516.83dd2c1b86b4e507.js} +2 -2
  17. package/apps/pwa/out/_next/static/chunks/{89849432.9eaa2f4c9c010965.js → 16e57f67.3ea19e893461e611.js} +3 -3
  18. package/apps/pwa/out/_next/static/chunks/1729-d73dd25e564679d4.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/{1505.5826671109111e75.js → 1743.563d3168e95825ef.js} +2 -2
  20. package/apps/pwa/out/_next/static/chunks/1843.5924c24295243261.js +27 -0
  21. package/apps/pwa/out/_next/static/chunks/1987.e28149724b92918b.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2066-5ca18ffa5a4be68b.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/2240.d10a66695155fd93.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2391.e2cbc98d4abbb41a.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2528.3868070ef1be69f9.js +24 -0
  26. package/apps/pwa/out/_next/static/chunks/2566-734cb83bbd405886.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2697.76ca2ac3efef6f3b.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/275.0e6bad414729bfa0.js +32 -0
  29. package/apps/pwa/out/_next/static/chunks/2803-33bdae771855a8d1.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2846.d6a433b8663819a5.js +309 -0
  31. package/apps/pwa/out/_next/static/chunks/3143.257997a5f1ed79df.js +34 -0
  32. package/apps/pwa/out/_next/static/chunks/{5967-c0a80f8ffe5d4050.js → 3259-86f3be56e43eaf6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/3280-e177c33bfbdcfb0a.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/3294-2b6a6d45a46a6c1c.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3309.364573ceedbdfd5f.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3522.758840fc5e7fe630.js +287 -0
  37. package/apps/pwa/out/_next/static/chunks/3525-a7c989c1a661e880.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3694.b1a66387756458c0.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/370.124f9e678a76fc01.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3797-9643105335e660fe.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{5578.ac963634373b28c6.js → 4084.15ecaf4240bd9e77.js} +2 -2
  42. package/apps/pwa/out/_next/static/chunks/4089-b3382b8843114eee.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/{8438.ee7f0df3b3f6fcb4.js → 4204.a409ab6250c0f6a8.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/4293.928a8d573c16aab7.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/4402.f9238a3b18a7bd43.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4490-9b090b0f9427f902.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4548.13502d0ca3b4f862.js +8 -0
  48. package/apps/pwa/out/_next/static/chunks/4565-67e4c620597965c8.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/{7905.b230b9d06cb4ad5d.js → 4593.10f5b9bd702d4a6c.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/4599-548bc96cda44a5a7.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/4625.206574e33a9e7ae8.js +104 -0
  52. package/apps/pwa/out/_next/static/chunks/4862-392c9a71f0d8dfb4.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5054.2906d1b02542edc5.js +149 -0
  54. package/apps/pwa/out/_next/static/chunks/{8569.b853a8daa8f31d12.js → 5081.a240b63fad28663a.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/5102-c822bbbee5a022a4.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5194-8c93f2e7e4b6f717.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5305-bacd7eca0b9a5848.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5311.9071ee3f44fd7ff5.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/5369.9f34353442daa421.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/{2528.0af5c0e1fad88df6.js → 5408.377c8c543635b81f.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{9502-32c5dc22e8f120f5.js → 5492-e05e26a033ba80d2.js} +3 -3
  62. package/apps/pwa/out/_next/static/chunks/5785-ea8d2c633b6688e3.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/589-49d730cb8138b397.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5904-264a6412ae3c9028.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/5980.728531d5c566535c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/599-a914cc0468f56143.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/6011.f525002e21bef0ac.js +23 -0
  68. package/apps/pwa/out/_next/static/chunks/6132.4381ab93683f1acf.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/6149.2e698635e454d8f6.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/{5974.360f8441eb0ac1ec.js → 6196.34d6b229079e9e28.js} +1 -1
  71. package/apps/pwa/out/_next/static/chunks/6356-0afafc4230cf9767.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6406.64756073f7df35b8.js +77 -0
  73. package/apps/pwa/out/_next/static/chunks/6479-16e62aedafa0e310.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6599.b5246b46a2453329.js +64 -0
  75. package/apps/pwa/out/_next/static/chunks/{ce383d63.d2ede338788ed18e.js → 65ebff95.081daeff622d238e.js} +1 -1
  76. package/apps/pwa/out/_next/static/chunks/6610.5c26eec011565acb.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/666.37d18474edc1c552.js +2 -0
  78. package/apps/pwa/out/_next/static/chunks/6904.a0b5b1ed4f2d2c20.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/{83cfc5c8.349535543c1548b6.js → 6def8a8e.c81f9ca41028eccf.js} +1 -1
  80. package/apps/pwa/out/_next/static/chunks/7247-1f4aba94aba50d9f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/7274-e71a7c5b7760cd02.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/7290.1fcaeef3adb5d0d4.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/7477-2c64e42760f82f33.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{2721.bc9a8f36bd85c07a.js → 7483.ba01c6c209b81bba.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/7505-47002b76ff1aef6c.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7600-11884ca4da68e5d9.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7607.40d8bb3689697ccb.js +62 -0
  88. package/apps/pwa/out/_next/static/chunks/{6702.ebbd165d5e4a4a14.js → 7752.5cbdd0b433c54af9.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/7763.f51fcc19ca1414ec.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7785.846140c859b1adf6.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7807-a0fdac6393e0bf36.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7837-20a63b4b7e9342b1.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/800.7815414cf280545d.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{8925-2abfb48af477fb7f.js → 8085-1e5fce59500a2cc2.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/8100.415f2105faa63e97.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/8290.701aa42ed13aa767.js +226 -0
  97. package/apps/pwa/out/_next/static/chunks/{b4e12462-5fab4a813a63dadd.js → 83a80514-b897b64df43ce108.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/8444.d9c74214a3145a43.js +93 -0
  99. package/apps/pwa/out/_next/static/chunks/8569.98c915534928c7f9.js +24 -0
  100. package/apps/pwa/out/_next/static/chunks/{8264.00e434fbd02e9311.js → 8578.dff22d988cc2b106.js} +3 -3
  101. package/apps/pwa/out/_next/static/chunks/8591.b1c7f876e5ec7cfb.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8715-f63756021d3ab989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8879.776c2ec7732aa114.js +115 -0
  104. package/apps/pwa/out/_next/static/chunks/8881.ddca15b3c82c490c.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8946.5da5c87f32a2e1a8.js +41 -0
  106. package/apps/pwa/out/_next/static/chunks/9117.080b9b75be90e14d.js +74 -0
  107. package/apps/pwa/out/_next/static/chunks/9167.f3ecb28f367d6e69.js +94 -0
  108. package/apps/pwa/out/_next/static/chunks/9224.66095370ee28c93b.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/9238-0682b013c83d6449.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/{7518.49161e8f5814abdb.js → 9311.24c8f5ea793253dd.js} +1 -1
  111. package/apps/pwa/out/_next/static/chunks/{9976.8f28864317639fe6.js → 94.4cfed21c0b781a06.js} +3 -3
  112. package/apps/pwa/out/_next/static/chunks/9449.d3ed795da4befb1c.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/949.1e7d45ccb71580aa.js +90 -0
  114. package/apps/pwa/out/_next/static/chunks/9638-d268329f2c6f4f8d.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/{0e3acd34.d42b2ef87fdf4234.js → 973cbf1b.3bbcf2b41548eca7.js} +1 -1
  116. package/apps/pwa/out/_next/static/chunks/9842-d6644eb53d3dc6bf.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9863.94ca3263e760696e.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/{6571-926ddf024a6ad5df.js → 9917-9e26abc5f7772735.js} +1 -1
  119. package/apps/pwa/out/_next/static/chunks/9938.b23e4d4dfa0114cd.js +79 -0
  120. package/apps/pwa/out/_next/static/chunks/{740.90b31555290236a9.js → 9974.e77b4ff4884557b3.js} +2 -2
  121. package/apps/pwa/out/_next/static/chunks/{4c5e95ce.83e9b16add6fcd8c.js → ab4d57c2.e90bc2cb31949045.js} +7 -7
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d9babd8795f396a5.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6a16a48d1aa6e91f.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-26345242e4049f55.js → page-fcb06e305f515527.js} +1 -1
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-39f82acf619cca23.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-7c2c8f81b185f9c0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d9babd8795f396a5.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-482c60c1bc540d0a.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-623e08fe4477d606.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-db460cd8aecb8490.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1917ea87ea71ab6b.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-25a302273b054fd7.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-670510eda8b9443f.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b92caddb5c715f30.js → page-15c0a4095baddfba.js} +1 -1
  153. package/apps/pwa/out/_next/static/chunks/app/page-4eb96fc7241c2456.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d64ff212dc55d03.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e47fc72049149a38.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-f7d808e31e005895.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-5c2b24b5496dad73.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d9babd8795f396a5.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-76cdb531668b5191.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-dab163b338e3b966.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-99c4e6c7345a9156.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/dcf24383-2f82e35f2d827393.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/{53746282.1be36038d1abd949.js → e117f801.e92bf10242653497.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/e87d89b9.2e71c0ae475d28a1.js +122 -0
  176. package/apps/pwa/out/_next/static/chunks/{0673cc54.8e2ef1c23e49e190.js → f4dfbfc0.a69fb12f854882aa.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/framework-bf135b997155e90e.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-5571537543bea91c.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-25c51c7df7f3a442.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-e3b2c1f5d82abd65.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-083f2e23e9bc3641.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-e94d22b7a028217b.js +1 -0
  183. package/apps/pwa/out/approvals/index.html +1 -1
  184. package/apps/pwa/out/approvals/index.txt +22 -22
  185. package/apps/pwa/out/autopilot/index.html +1 -1
  186. package/apps/pwa/out/autopilot/index.txt +22 -22
  187. package/apps/pwa/out/board/index.html +1 -1
  188. package/apps/pwa/out/board/index.txt +22 -22
  189. package/apps/pwa/out/botlab/index.html +1 -1
  190. package/apps/pwa/out/botlab/index.txt +21 -21
  191. package/apps/pwa/out/bots/index.html +1 -1
  192. package/apps/pwa/out/bots/index.txt +21 -21
  193. package/apps/pwa/out/ceo/index.html +1 -1
  194. package/apps/pwa/out/ceo/index.txt +22 -22
  195. package/apps/pwa/out/chat/index.html +1 -1
  196. package/apps/pwa/out/chat/index.txt +21 -21
  197. package/apps/pwa/out/consult/index.html +1 -1
  198. package/apps/pwa/out/consult/index.txt +22 -22
  199. package/apps/pwa/out/control/index.html +1 -1
  200. package/apps/pwa/out/control/index.txt +22 -22
  201. package/apps/pwa/out/decisions/index.html +1 -1
  202. package/apps/pwa/out/decisions/index.txt +21 -21
  203. package/apps/pwa/out/design-check/index.html +1 -1
  204. package/apps/pwa/out/design-check/index.txt +22 -22
  205. package/apps/pwa/out/editor/index.html +1 -1
  206. package/apps/pwa/out/editor/index.txt +22 -22
  207. package/apps/pwa/out/exec/index.html +1 -1
  208. package/apps/pwa/out/exec/index.txt +22 -22
  209. package/apps/pwa/out/field/index.html +1 -1
  210. package/apps/pwa/out/field/index.txt +22 -22
  211. package/apps/pwa/out/healthz +1 -1
  212. package/apps/pwa/out/index.html +1 -1
  213. package/apps/pwa/out/index.txt +21 -21
  214. package/apps/pwa/out/inside/index.html +1 -1
  215. package/apps/pwa/out/inside/index.txt +21 -21
  216. package/apps/pwa/out/intake/index.html +1 -1
  217. package/apps/pwa/out/intake/index.txt +22 -22
  218. package/apps/pwa/out/live/index.html +1 -1
  219. package/apps/pwa/out/live/index.txt +22 -22
  220. package/apps/pwa/out/live-run/index.html +1 -1
  221. package/apps/pwa/out/live-run/index.txt +22 -22
  222. package/apps/pwa/out/loops/index.html +1 -1
  223. package/apps/pwa/out/loops/index.txt +21 -21
  224. package/apps/pwa/out/market/index.html +1 -1
  225. package/apps/pwa/out/market/index.txt +21 -21
  226. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  227. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  228. package/apps/pwa/out/morning/index.html +1 -1
  229. package/apps/pwa/out/morning/index.txt +22 -22
  230. package/apps/pwa/out/observatory/index.html +1 -1
  231. package/apps/pwa/out/observatory/index.txt +22 -22
  232. package/apps/pwa/out/ops/checklist/index.html +1 -1
  233. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  234. package/apps/pwa/out/ops/release/index.html +1 -1
  235. package/apps/pwa/out/ops/release/index.txt +21 -21
  236. package/apps/pwa/out/ops/seats/index.html +1 -1
  237. package/apps/pwa/out/ops/seats/index.txt +21 -21
  238. package/apps/pwa/out/outputs/index.html +1 -1
  239. package/apps/pwa/out/outputs/index.txt +21 -21
  240. package/apps/pwa/out/reflection/index.html +1 -1
  241. package/apps/pwa/out/reflection/index.txt +22 -22
  242. package/apps/pwa/out/scheduler/index.html +1 -1
  243. package/apps/pwa/out/scheduler/index.txt +22 -22
  244. package/apps/pwa/out/sessions/index.html +1 -1
  245. package/apps/pwa/out/sessions/index.txt +21 -21
  246. package/apps/pwa/out/settings/devices/index.html +1 -1
  247. package/apps/pwa/out/settings/devices/index.txt +22 -22
  248. package/apps/pwa/out/settings/index.html +1 -1
  249. package/apps/pwa/out/settings/index.txt +22 -22
  250. package/apps/pwa/out/setup/done/index.html +1 -1
  251. package/apps/pwa/out/setup/done/index.txt +22 -22
  252. package/apps/pwa/out/setup/index.html +1 -1
  253. package/apps/pwa/out/setup/index.txt +22 -22
  254. package/apps/pwa/out/share/index.html +1 -1
  255. package/apps/pwa/out/share/index.txt +22 -22
  256. package/apps/pwa/out/showroom/index.html +1 -1
  257. package/apps/pwa/out/showroom/index.txt +21 -21
  258. package/apps/pwa/out/tasks/index.html +1 -1
  259. package/apps/pwa/out/tasks/index.txt +22 -22
  260. package/apps/pwa/out/term/index.html +1 -1
  261. package/apps/pwa/out/term/index.txt +22 -22
  262. package/apps/pwa/out/today/index.html +1 -1
  263. package/apps/pwa/out/today/index.txt +22 -22
  264. package/apps/pwa/out/trace/index.html +1 -1
  265. package/apps/pwa/out/trace/index.txt +22 -22
  266. package/apps/pwa/out/vault/index.html +1 -1
  267. package/apps/pwa/out/vault/index.txt +22 -22
  268. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  269. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  270. package/apps/pwa/out/workflows/index.html +1 -1
  271. package/apps/pwa/out/workflows/index.txt +22 -22
  272. package/apps/pwa/out/workspace/index.html +1 -1
  273. package/apps/pwa/out/workspace/index.txt +22 -22
  274. package/apps/pwa/out/worktrees/index.html +1 -1
  275. package/apps/pwa/out/worktrees/index.txt +22 -22
  276. package/graphs/release/release-loop.yaml +5 -5
  277. package/graphs/steward/recipes.yaml +4 -0
  278. package/graphs/steward/steward.yaml +10 -1
  279. package/package.json +1 -1
  280. package/scripts/bench-report.ts +46 -1
  281. package/scripts/ci-typecheck-changed.ts +88 -7
  282. package/scripts/coord-msg-bridge.ts +209 -0
  283. package/scripts/coord-post.sh +40 -7
  284. package/scripts/coord-requests-bridge.ts +10 -5
  285. package/scripts/expose-rubric-suggest.ts +1 -1
  286. package/scripts/measure-tsc-affected-roots.ts +10 -1
  287. package/scripts/release-loop/auto-approve-node.ts +6 -0
  288. package/scripts/release-loop/export-check-node.ts +8 -5
  289. package/scripts/release-loop/gate-closure.ts +286 -0
  290. package/scripts/release-loop/gate-node.ts +73 -13
  291. package/scripts/release-loop/gate-partial.ts +34 -6
  292. package/scripts/release-loop/node-verdict.ts +7 -2
  293. package/scripts/release-loop/ops-upgrade-node.ts +63 -42
  294. package/scripts/release-loop/publish-node.ts +6 -0
  295. package/scripts/release-loop/resume-release.ts +37 -10
  296. package/scripts/release-loop/resume.ts +69 -4
  297. package/scripts/tsc-baseline-cache.ts +28 -3
  298. package/scripts/usage-rollup.ts +27 -13
  299. package/scripts/ux-sim.ts +30 -1
  300. package/skills/youtube-master/scripts/main.ts +4 -1
  301. package/skills/youtube-master/src/obsidian.ts +9 -0
  302. package/src/agent/task-notification.ts +30 -1
  303. package/src/agent-substrate/acp-implementer.ts +14 -7
  304. package/src/agent-substrate/pr-reviewer.ts +30 -10
  305. package/src/agent-substrate/review-observation.ts +9 -2
  306. package/src/agent-substrate/self-review-cli.ts +154 -16
  307. package/src/boot/daemon-tools/self-implement.ts +72 -4
  308. package/src/budget/cost-tier-advisor.ts +91 -0
  309. package/src/budget/llm-cost.ts +5 -1
  310. package/src/budget/shadow-arms.ts +77 -0
  311. package/src/budget/types.ts +5 -3
  312. package/src/budget/unified-usage.ts +172 -8
  313. package/src/cli/control-cli.ts +29 -0
  314. package/src/cli/doctor-cli.ts +106 -3
  315. package/src/cli/friendly-errors.ts +113 -0
  316. package/src/cli/intake-cli.ts +3 -1
  317. package/src/cli/memory-cli.ts +182 -0
  318. package/src/cli/model-watch-cli.ts +4 -1
  319. package/src/cli/msg-cli.ts +15 -5
  320. package/src/cli/pr-cli.ts +53 -0
  321. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  322. package/src/cli/pr-land-pod-shadow.ts +54 -0
  323. package/src/cli/release-cli.ts +41 -12
  324. package/src/cli/release-run-if-ready.ts +11 -0
  325. package/src/cli/resource-cli.ts +48 -0
  326. package/src/cli/retro-cli.ts +12 -3
  327. package/src/cli/seat-cli.ts +121 -0
  328. package/src/cli/seat-requests-cli.ts +25 -0
  329. package/src/cli/self-utterances-cli.ts +140 -0
  330. package/src/cli/tasks-cli.ts +168 -14
  331. package/src/cli/usage-cli.ts +3 -1
  332. package/src/compact/verify-probe.ts +23 -3
  333. package/src/context-card/cell.ts +40 -0
  334. package/src/context-card/index.ts +50 -0
  335. package/src/control/control-status.ts +30 -1
  336. package/src/dashboard/boot-timing.ts +35 -0
  337. package/src/dashboard/index.ts +86 -24
  338. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  339. package/src/dashboard/slash-runtime/dashboard-handlers.ts +10 -138
  340. package/src/dashboard/slash-runtime/shell-slash.ts +145 -0
  341. package/src/debug/log-diet.ts +16 -4
  342. package/src/debug/log.ts +1 -1
  343. package/src/domains/morning-essential.ts +18 -3
  344. package/src/domains/release-tool.ts +29 -8
  345. package/src/domains/surface-events.ts +16 -0
  346. package/src/elanous-config-dir.ts +2 -2
  347. package/src/execution-loop/launch-gate.ts +158 -2
  348. package/src/fleet/host-doctor.ts +446 -0
  349. package/src/goals/judge.ts +20 -6
  350. package/src/graph-runner/runner.ts +12 -6
  351. package/src/graph-wizard/archetypes.ts +14 -8
  352. package/src/graph-wizard/generate.ts +77 -1
  353. package/src/grok.ts +13 -7
  354. package/src/harness/harness-cli-command.ts +102 -51
  355. package/src/harness/harness-stop.ts +93 -19
  356. package/src/index.ts +52 -179
  357. package/src/instance/current.ts +2 -6
  358. package/src/instance/resolve.ts +5 -6
  359. package/src/intake-plane/collect-github.ts +7 -2
  360. package/src/intake-plane/lens.ts +46 -4
  361. package/src/intake-plane/requirement-funnel.ts +53 -1
  362. package/src/intelligence-map/catalog-feed.ts +119 -0
  363. package/src/intelligence-map/model-watch-intake.ts +40 -0
  364. package/src/intelligence-map/model-watch-mission.ts +14 -1
  365. package/src/llm/open-weight-discipline.ts +21 -0
  366. package/src/llm/openai-sampling-fields.ts +89 -0
  367. package/src/llm/runtime-model-family.ts +61 -0
  368. package/src/llm/zai-coding-plan.ts +27 -1
  369. package/src/llm.ts +17 -97
  370. package/src/machines/machine-ledger.ts +31 -3
  371. package/src/maturity/cli-maturity.ts +13 -3
  372. package/src/maturity/feature-maturity.ts +1 -1
  373. package/src/msg/msg-store.ts +69 -7
  374. package/src/nexus/api/http-server.ts +22 -0
  375. package/src/oauth/codex-account-rotation.ts +4 -1
  376. package/src/oauth/codex-account-store.ts +20 -7
  377. package/src/oauth/codex-quota-policy.ts +33 -4
  378. package/src/ops/overview-collect.ts +3 -2
  379. package/src/ops/overview-owner.ts +29 -0
  380. package/src/plugin-hooks/shell-hook.ts +17 -2
  381. package/src/plugin-missions/evaluator-runner.ts +18 -2
  382. package/src/plugins/install/plugin-install.ts +11 -1
  383. package/src/preview/terminal.ts +15 -5
  384. package/src/project/project-store.ts +20 -0
  385. package/src/prompt-enhance/enhance.ts +11 -0
  386. package/src/release-loop/auto-start.ts +7 -5
  387. package/src/release-loop/checklist.ts +52 -5
  388. package/src/release-loop/feature-store.ts +10 -9
  389. package/src/release-loop/gate-shards.ts +4 -0
  390. package/src/release-loop/light-rc.ts +84 -13
  391. package/src/release-loop/merged-pr-checklist.ts +35 -5
  392. package/src/release-loop/rehearsal.ts +1 -1
  393. package/src/seat-loop/duty-ledger.ts +64 -0
  394. package/src/seat-loop/seat-loop.ts +5 -1
  395. package/src/self-dev/decompose.ts +37 -3
  396. package/src/self-dev/dev-pipeline.ts +32 -7
  397. package/src/self-dev/draft-residue.ts +16 -6
  398. package/src/self-dev/draft-sweep.ts +75 -18
  399. package/src/self-dev/fabric-decompose-adapter.ts +9 -2
  400. package/src/self-dev/orchestrate.ts +6 -2
  401. package/src/self-dev/run-supervisor.ts +72 -8
  402. package/src/self-implement/gate-cli.ts +29 -10
  403. package/src/self-implement/gate-remote.ts +4 -1
  404. package/src/self-implement/goal-author.ts +15 -29
  405. package/src/self-implement/goal-command-execution.ts +26 -0
  406. package/src/self-implement/goal-new-paths.ts +42 -0
  407. package/src/self-implement/host-regate.ts +124 -30
  408. package/src/self-implement/importer-test-index.ts +3 -1
  409. package/src/self-implement/l8-merge-queue.ts +80 -2
  410. package/src/self-implement/merge-train.ts +19 -0
  411. package/src/self-implement/off-diff-evidence.ts +40 -1
  412. package/src/self-implement/orchestrator.ts +90 -20
  413. package/src/self-implement/premise-review.ts +159 -0
  414. package/src/self-implement/pwa-import-graph.ts +194 -7
  415. package/src/self-implement/regate-detail.ts +79 -0
  416. package/src/self-implement/review-deferred-to-host.ts +21 -0
  417. package/src/self-implement/rework-budget-judgment.ts +23 -3
  418. package/src/self-implement/running-runs.ts +2 -2
  419. package/src/self-implement/scope-adjacency.ts +23 -0
  420. package/src/self-implement/seams.ts +42 -7
  421. package/src/self-implement/self-implement-runtime.ts +126 -21
  422. package/src/session/index.ts +14 -4
  423. package/src/session-runtime/index.ts +55 -11
  424. package/src/skills/tools/agent.ts +16 -1
  425. package/src/skills/tools/persistent-grounding.ts +11 -1
  426. package/src/skills/tools/shell.ts +33 -1
  427. package/src/steward/rejudge-tick.ts +142 -0
  428. package/src/task-agent/board.ts +92 -3
  429. package/src/task-agent/card-evidence.ts +88 -1
  430. package/src/task-agent/cover.ts +85 -1
  431. package/src/task-agent/live-moves.ts +14 -6
  432. package/src/task-agent/parent-host.ts +27 -3
  433. package/src/task-agent/rejudge-on-head.ts +370 -29
  434. package/src/task-agent/review-result-capture.ts +67 -2
  435. package/src/task-agent/shadow.ts +148 -21
  436. package/src/task-agent/task-hand.ts +22 -3
  437. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -0
  438. package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +122 -0
  439. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +23 -0
  440. package/src/task-orchestrator/surfaces/pod-pool.ts +53 -0
  441. package/src/task-orchestrator/surfaces/self-implement-pod.ts +236 -28
  442. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  443. package/src/tui.ts +20 -1
  444. package/src/user-config.ts +21 -63
  445. package/src/ux-sim/fault-cred-expiry.ts +100 -0
  446. package/src/ux-sim/fault-pod-death.ts +74 -0
  447. package/src/ux-sim/fault-pool-cap.ts +81 -0
  448. package/src/version/packed-revision.json +1 -1
  449. package/src/webclone/qa-table.ts +22 -0
  450. package/apps/pwa/out/_next/static/chunks/0a09e062-34f747a17d8d8c2e.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +0 -77
  452. package/apps/pwa/out/_next/static/chunks/1325-dc3f3c21eb181cb4.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +0 -34
  454. package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +0 -24
  455. package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/1669-da843ed73aa301f5.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +0 -36
  465. package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +0 -122
  466. package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +0 -62
  468. package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/272-0eed0fb0aab716b6.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +0 -149
  475. package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/3961-aaae04ed1d32622f.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +0 -309
  479. package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/5313.1697309c532b9856.js +0 -2
  491. package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +0 -74
  499. package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +0 -64
  503. package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +0 -32
  506. package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +0 -24
  507. package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +0 -93
  511. package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +0 -148
  516. package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +0 -226
  517. package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +0 -27
  518. package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +0 -115
  521. package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +0 -8
  528. package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +0 -23
  529. package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +0 -90
  534. package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +0 -94
  535. package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +0 -41
  537. package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +0 -287
  539. package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +0 -79
  540. package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +0 -104
  545. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3b9fc39362473ae7.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/app/board/page-d7602d489edf5f37.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/app/botlab/page-24282bf9f90031a5.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/app/ceo/page-4eb84d454f8c3415.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/app/chat/page-b2e494bdad57fcb5.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/app/consult/page-c0ab229673a8b0eb.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/app/editor/page-bf88c0f558d8ef54.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/app/exec/page-52fec523e5b5d3ad.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/app/inside/page-22725e2570f1126b.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/intake/page-1891335950d68b70.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/live/page-c314b75c11234998.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/live-run/page-9f9c85415f61782b.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8526c17ae38e6a50.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/morning/page-f998637a8402115c.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6f23159fedb41f67.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1d809a87139dd024.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-da9e570234d481fd.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/reflection/page-57ffeeb6019440fd.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2c2b3f29cec1efa4.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-9369279f72fc7b51.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b5c0c996f329371e.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/tasks/page-89881ccbcf728978.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/today/page-58d4e5cb30ef4871.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-acf868c64d77f950.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/workspace/page-bc79f0833533fa84.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d5c4398eea03d8f3.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/framework-b2763b10dc6b3836.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +0 -1
  601. /package/apps/pwa/out/_next/static/{Q-7iofoxOEv1WX18S35of → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
@@ -0,0 +1,23 @@
1
+ import { BUDGET_SNAPSHOT_ENV } from '../../execution-loop/launch-gate.js';
2
+
3
+ // Preserve the manifest contract except for two values generated per launch.
4
+ export function stablePodManifestText(applied: Array<Record<string, any>>): string {
5
+ const manifests = structuredClone(applied.filter((entry) => entry.kind === 'Job' || entry.kind === 'Secret'));
6
+ let snapshots = 0;
7
+ for (const manifest of manifests) {
8
+ if (manifest.kind !== 'Job') continue;
9
+ for (const container of manifest.spec?.template?.spec?.containers ?? []) {
10
+ for (const entry of container.env ?? []) {
11
+ if (entry.name !== BUDGET_SNAPSHOT_ENV) continue;
12
+ snapshots++;
13
+ // Keep every original byte except this one volatile numeric value.
14
+ const measuredAt = /("measuredAt"\s*:\s*)\d+/g;
15
+ const matches = [...entry.value.matchAll(measuredAt)];
16
+ if (matches.length !== 1) throw new Error('budget snapshot must have exactly one numeric measuredAt');
17
+ entry.value = entry.value.replace(measuredAt, (_match: string, prefix: string) => `${prefix}0`);
18
+ }
19
+ }
20
+ }
21
+ if (snapshots === 0) throw new Error('manifest comparison requires a budget snapshot');
22
+ return JSON.stringify(manifests).replace(/run-[0-9a-f-]{36}/g, 'run-<id>');
23
+ }
@@ -154,6 +154,59 @@ export function parsePodPool(spec: string): PodPoolMember[] {
154
154
  */
155
155
  export type PodPlacementKind = 'lite' | 'standard';
156
156
 
157
+ /** Identity carried by a launched pool Job and its Pod; absent source fields stay absent. */
158
+ export interface PodWorkIdentity {
159
+ ownerKind?: 'seat' | 'steward' | 'orchestrator' | 'external';
160
+ ownerId?: string;
161
+ taskCard?: string;
162
+ slotId?: string;
163
+ work?: 'harness' | 'authoring' | 'lite';
164
+ }
165
+
166
+ /** Kubernetes label values cannot exceed 63 characters or contain arbitrary card/owner text. */
167
+ export function podWorkLabels(identity: PodWorkIdentity): Record<string, string> {
168
+ const value = (raw: string | undefined): string | undefined => {
169
+ const cleaned = (raw ?? '').replace(/[^A-Za-z0-9._-]+/g, '-').slice(0, 63).replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, '');
170
+ return cleaned || undefined;
171
+ };
172
+ const ownerId = identity.ownerId && value(identity.ownerId) === identity.ownerId ? identity.ownerId : undefined;
173
+ const taskCard = identity.taskCard && value(identity.taskCard) === identity.taskCard ? identity.taskCard : undefined;
174
+ const slotId = identity.slotId && value(identity.slotId) === identity.slotId ? identity.slotId : undefined;
175
+ return {
176
+ ...(identity.ownerKind && ownerId ? { 'elanous.owner-kind': identity.ownerKind, 'elanous.owner-id': ownerId } : {}),
177
+ ...(taskCard ? { 'elanous.task-card': taskCard } : {}),
178
+ ...(slotId ? { 'elanous.checklist': slotId } : {}),
179
+ ...(identity.work ? { 'elanous.work': identity.work } : {}),
180
+ };
181
+ }
182
+
183
+ /** Minih standard-Job policy, independent of the configured pool member capacity. Not wired into allocation. */
184
+ export type MinihLocation = 'home' | 'external';
185
+
186
+ export function minihStandardCapacity(location: MinihLocation): number {
187
+ return location === 'home' ? 1 : 2;
188
+ }
189
+
190
+ /** A drain withholds new slots until occupancy falls to the target; it never evicts Running Pods. */
191
+ export function decideMinihDrain(location: MinihLocation, runningPods: number, pendingPods = 0): {
192
+ capacity: number;
193
+ runningPods: number;
194
+ drainNewAdmissions: boolean;
195
+ availableSlots: number;
196
+ } {
197
+ if (![runningPods, pendingPods].every((count) => Number.isSafeInteger(count) && count >= 0)) {
198
+ throw new RangeError('node-c occupancy must be non-negative whole Pods');
199
+ }
200
+ const capacity = minihStandardCapacity(location);
201
+ const occupied = runningPods + pendingPods;
202
+ return {
203
+ capacity,
204
+ runningPods,
205
+ drainNewAdmissions: occupied > capacity,
206
+ availableSlots: Math.max(0, capacity - occupied),
207
+ };
208
+ }
209
+
157
210
  export interface PodPlacement {
158
211
  readonly member: string;
159
212
  readonly kind: PodPlacementKind | null;
@@ -28,6 +28,8 @@ import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
28
28
  import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
29
29
  import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
30
30
  import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
31
+ import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
32
+ import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
31
33
  import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
32
34
 
33
35
  export const POD_CONTROL_INBOX_DIR = '/tmp/elanous-control.inbox';
@@ -50,7 +52,7 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
50
52
  // POD-DIET (10-06 03:47~05:18 · node-b goal Pods · 89 one-minute samples · 42 Pods): per-Pod observed max median 4.6Gi ·
51
53
  // p75 6.2Gi · p90 11.7Gi — 23/42 went above the old 4Gi request, so the scheduler over-packed. 6Gi ≈ p75; limits stay.
52
54
  // Node allocatable ≈ 343Gi ≫ 25 × 6Gi. One-minute samples can miss the true peak (lower bound).
53
- import type { PodPlacement, PodPlacementKind, PodPoolMember, PodPoolScheduler } from './pod-pool.js';
55
+ import { podWorkLabels, type PodWorkIdentity, type PodPlacement, type PodPlacementKind, type PodPoolMember, type PodPoolScheduler } from './pod-pool.js';
54
56
  export { POD_CHILD_REQUESTS } from './pod-lease.js';
55
57
  import { measurePoolLease, recommendConcurrency, POD_CHILD_REQUESTS, POD_HOST_LEASE_ANNOTATION, POD_JOB_TTL_AFTER_FINISHED_SECONDS, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
56
58
  import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
@@ -67,9 +69,11 @@ import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-d
67
69
  import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
68
70
  import { selectLiveDetail } from '../../live/detail-switch.js';
69
71
  import { authStorePath } from '../../oauth/store.js';
70
- import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
72
+ import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential, type GrokRefreshOutcome } from '../../grok/credential.js';
71
73
  import { defaultGrokModel } from '../../grok/models.js';
72
74
  import { resolveHostId } from '../../platform/host-id.js';
75
+ import { BUDGET_SNAPSHOT_ENV, encodeBudgetSnapshot, harnessLaunchBudgetDecision, type BudgetSnapshot } from '../../execution-loop/launch-gate.js';
76
+ import type { ReadBudgetInputsDeps } from '../../self-implement/budget-gate.js';
73
77
  import { envLiteral } from '../../platform/env-literal.js';
74
78
  import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } from '../../model-tier/llm-tier-map.js';
75
79
  import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
@@ -84,7 +88,7 @@ import { resolveBedrockRegion, validBedrockRegion } from '../../llm/bedrock.js';
84
88
  import { ZAI_POD_EGRESS_LABEL, checkZaiPodSecret, countActiveZaiJobs, decideZaiPodSlot, zaiOverflowChildArgs, zaiPodEnv, type ZaiPodSpec } from './pod-zai-child.js';
85
89
  import { ZAI_CODING_DEFAULT_MODEL, ZAI_OPENROUTER_FALLBACK_MODEL, zaiSettingsFromConfig, type ZaiCodingPlanSettings } from '../../llm/zai-coding-plan.js';
86
90
  import { selectEnhanceLlm } from '../../prompt-enhance/enhance.js';
87
- import { extractPodFailureReason, podTerminalRow } from './pod-failure-reason.js';
91
+ import { classifyPodExit, extractPodFailureReason, podTerminalRow, type PodExitClass } from './pod-failure-reason.js';
88
92
  import { OLD_DOOR_STAMP_ENV } from '../../self-dev/old-door.js';
89
93
 
90
94
  export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
@@ -110,6 +114,10 @@ export interface PodSpawnOptions {
110
114
  rotationAccounts?: readonly string[];
111
115
  /** 미지정이면 종전 Codex Job. */
112
116
  provider?: PodChildProvider;
117
+ /** Host launch-gate verdict for this Job's child LLM (test seam) — the Pod gate follows it instead of judging again. Default: the host's own `harness` launch-gate decision (chain ⊕ user caps). */
118
+ budgetSnapshot?: (childLlm: PodBudgetChildLlm) => Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined;
119
+ /** Input reader seam; the host still runs its normal budget decision and Job propagation. */
120
+ budgetInputDeps?: ReadBudgetInputsDeps & { readLiveGrok?: () => Promise<number | undefined> };
113
121
  /** Child model/effort chosen at launch (`--child-llm-model/--child-llm-effort`) — carried into the Pod child. */
114
122
  childModel?: string;
115
123
  childEffort?: string;
@@ -142,11 +150,17 @@ export interface PodSpawnOptions {
142
150
  /** Job 수명 상한(초). */
143
151
  deadlineSeconds?: number;
144
152
  pollMs?: number;
153
+ /** METER-POD-ROLLUP-GAP-3 — 런 도중 PARTIAL 표본을 거두는 폴 간격 수(시험 seam · 기본 POD_USAGE_PARTIAL_CAPTURE_MS ÷ pollMs). */
154
+ usagePartialCapturePolls?: number;
145
155
  /** LAUNCH-STALL: ms a launch may wait in one stage before `launch-stalled` (test seam; default harness.launchStallMinutes · 15 min). */
146
156
  launchStallMs?: number;
147
157
  kubectl?: Kubectl;
148
158
  /** Host-side regate (injected for Pod tests). */
149
159
  hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; noMerge?: true }) => Promise<HostRegateResult>;
160
+ /** REVIEW-ACP-POD — Pod merge-ready PR 을 호스트 Claude ACP 로 리뷰한 뒤 재게이트(opt-in · 옵션 → env `ELANOUS_POD_HOST_ACP_REVIEW` → config `llm.podHostAcpReview`). */
161
+ hostAcpReview?: boolean;
162
+ /** REVIEW-ACP-POD — 호스트 ACP 리뷰 seam(시험 주입 · 기본 `runPodHostAcpReview`). */
163
+ podHostAcpReview?: (input: { prNumber: number; headCommit: string; repoRoot: string; job?: string }) => Promise<PodHostAcpReviewOutcome>;
150
164
  /**
151
165
  * TA-LIVE-LAND-2 — `taskAgent.liveMoves` (test seam; default reads the user config). Consulted only when the run carries
152
166
  * the `ELANOUS_TA_LAND=1` marker (`tasks hand --ta-land`): with `propose-land` the host re-gates but leaves the merge to
@@ -221,6 +235,52 @@ export function podJobName(spaceId: string): string {
221
235
  return `si-${slug || 'job'}-${hash}`.replace(/-+/g, '-').slice(0, 58);
222
236
  }
223
237
 
238
+ /** The child LLM the Pod's delegated `harness say/ask` will be launched with — the question the host verdict answers. */
239
+ export interface PodBudgetChildLlm { provider?: string; model?: string }
240
+
241
+ /** The one host-side budget verdict a Pod Job carries (BUDGET-SNAPSHOT-TO-POD). Fail-soft: no snapshot means the Pod marks the budget unmeasured. */
242
+ function hostBudgetSnapshot(childLlm: PodBudgetChildLlm, options: PodSpawnOptions, env: NodeJS.ProcessEnv, job: string): Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined {
243
+ const unavailable = (error: unknown): undefined => {
244
+ try { debug.log('self-implement.pod', 'budget-snapshot-unavailable', { job, provider: childLlm.provider ?? null, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); } catch { /* observation is fail-soft */ }
245
+ return undefined;
246
+ };
247
+ let measurement: Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined;
248
+ try {
249
+ measurement = options.budgetSnapshot ? options.budgetSnapshot(childLlm) : measureHostBudgetSnapshot(childLlm, env, options.budgetInputDeps);
250
+ } catch (error) { return unavailable(error); }
251
+ if (!(measurement instanceof Promise)) return measurement;
252
+ let timer: ReturnType<typeof setTimeout> | undefined;
253
+ const deadline = new Promise<undefined>((resolve) => {
254
+ timer = setTimeout(() => resolve(unavailable(new Error('host budget snapshot timed out after 10s'))), 10_000);
255
+ });
256
+ return Promise.race([measurement, deadline]).catch(unavailable).finally(() => { if (timer) clearTimeout(timer); });
257
+ }
258
+
259
+ /** The host runs the same launch-gate judgement the Pod would (chain ⊕ the user's caps) and ships the verdict, not raw usage to re-judge. */
260
+ async function measureHostBudgetSnapshot(childLlm: PodBudgetChildLlm, env: NodeJS.ProcessEnv, inputDeps?: ReadBudgetInputsDeps & { readLiveGrok?: () => Promise<number | undefined> }): Promise<BudgetSnapshot | undefined> {
261
+ const host = resolveHostId(env);
262
+ const measuredAt = Date.now();
263
+ const { readBudgetInputsLive, CODEX_PROVIDER, GROK_PROVIDER } = await import('../../self-implement/budget-gate.js');
264
+ const inputs = await readBudgetInputsLive(inputDeps);
265
+ const { decision } = harnessLaunchBudgetDecision(inputs, { ...(childLlm.provider ? { childLlmProvider: childLlm.provider } : {}), ...(childLlm.model ? { childLlmModel: childLlm.model } : {}) });
266
+ const provider = decision.provider ?? childLlm.provider ?? 'chain';
267
+ const used = provider === GROK_PROVIDER
268
+ ? inputs.grokUsedPercent
269
+ : provider === CODEX_PROVIDER
270
+ ? (() => {
271
+ const percents = inputs.codexCandidates
272
+ .map((candidate) => candidate.reached === true ? 100 : candidate.usedPercent)
273
+ .filter((percent): percent is number => typeof percent === 'number' && Number.isFinite(percent));
274
+ return percents.length === 0 ? undefined : Math.min(...percents);
275
+ })()
276
+ : undefined;
277
+ return {
278
+ provider, ...(used !== undefined ? { remaining: Math.min(100, Math.max(0, 100 - used)) } : {}), window: 'weekly', measuredAt, host,
279
+ ...(childLlm.provider ? { requested: childLlm.provider } : {}),
280
+ decision: { action: decision.action, ...(decision.provider ? { provider: decision.provider } : {}), ...(decision.model ? { model: decision.model } : {}) },
281
+ };
282
+ }
283
+
224
284
  /** «3시간 안 만료» — 선갱신 대상이라는 표지(다른 자격 오류와 구분한다). */
225
285
  export class PodCodexExpiringError extends Error {
226
286
  constructor(readonly account: string, readonly hoursLeft: number) {
@@ -403,10 +463,16 @@ export type PodGrokSkipReason = 'missing' | 'expiring' | 'refresh-failed';
403
463
 
404
464
  /** GROK-OPTIONAL (10-05) — may this launch plan put grok in the Pod's chain at all?
405
465
  * The Pod copy must outlive the Job (POD_GROK_MIN_VALIDITY_MS), so an expiring subscription that the host cannot
406
- * refresh is «not available» here: the plan drops grok and goes on with codex instead of failing every launch. */
466
+ * refresh is «not available» here: the plan drops grok and goes on with codex instead of failing every launch.
467
+ * AUT-GROK-CRED-EXPIRY-NO-LAUNCH (10-12): the refresh is already attempted once (`grok models`), but
468
+ * `refreshGrokSubscriptionToken` reports failure as a «value», never a throw — so a missing/broken CLI used to be
469
+ * read as `expiring`. The outcome is now classified (`failed`/`no-credential` → `refresh-failed`, `unchanged` →
470
+ * `expiring`) and every unusable verdict also lands as `harness.pod`/`grok-credential-unusable` so the autopsy and
471
+ * the dispatcher can group same-cause no-launches. ⛔ Never switches the provider here (billing path unchanged). */
407
472
  export function podGrokSubscriptionUsable(opts: {
408
473
  home?: string; env?: NodeJS.ProcessEnv;
409
- refresh?: () => void; isExpiring?: () => boolean; expiresAt?: () => string | null;
474
+ /** Test seam — the host refresh. A returned outcome is classified; `void` counts as an attempt that did not fail. */
475
+ refresh?: () => GrokRefreshOutcome | void; isExpiring?: () => boolean; expiresAt?: () => string | null;
410
476
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
411
477
  } = {}): { usable: true } | { usable: false; reason: PodGrokSkipReason; expiresAt: string | null } {
412
478
  const log = opts.log ?? ((category, event, data) => debug.log(category, event, data));
@@ -418,19 +484,35 @@ export function podGrokSubscriptionUsable(opts: {
418
484
  return all.sort().at(-1) ?? null;
419
485
  } catch { return null; }
420
486
  };
421
- const skip = (reason: PodGrokSkipReason) => {
487
+ const skip = (reason: PodGrokSkipReason, refreshOutcome: GrokRefreshOutcome | 'threw' | 'not-attempted') => {
422
488
  const at = expiresAt();
423
- try { log('pod.grok-credentials', 'skipped', { reason, expiresAt: at }); } catch { /* observation is fail-soft */ }
489
+ try { log('pod.grok-credentials', 'skipped', { reason, expiresAt: at, refreshOutcome }); } catch { /* observation is fail-soft */ }
490
+ try { log('harness.pod', 'grok-credential-unusable', { reason, expiresAt: at, refreshOutcome, minValidityMs: POD_GROK_MIN_VALIDITY_MS }); } catch { /* fail-soft */ }
424
491
  return { usable: false as const, reason, expiresAt: at };
425
492
  };
426
493
  const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
427
- if (credential?.kind !== 'subscription') return skip('missing');
494
+ if (credential?.kind !== 'subscription') return skip('missing', 'not-attempted');
428
495
  const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS }));
429
496
  if (!expiring()) return { usable: true };
430
- let refreshed = true;
431
- try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { refreshed = false; }
497
+ let outcome: GrokRefreshOutcome | 'threw' = 'unchanged';
498
+ try { outcome = (opts.refresh ?? (() => refreshGrokSubscriptionToken({ home: opts.home })))() ?? 'unchanged'; } catch { outcome = 'threw'; }
432
499
  if (!expiring()) return { usable: true };
433
- return skip(refreshed ? 'expiring' : 'refresh-failed');
500
+ return skip(outcome === 'failed' || outcome === 'no-credential' || outcome === 'threw' ? 'refresh-failed' : 'expiring', outcome);
501
+ }
502
+
503
+ /** AUT-GROK-CRED-EXPIRY-NO-LAUNCH — read-only launch check for dispatchers: no refresh, no CLI spawn, no log.
504
+ * `ready:false` means a Pod launch that names grok will be refused (or drop grok) unless a refresh extends the token. */
505
+ export function grokLaunchReadiness(opts: { home?: string; env?: NodeJS.ProcessEnv; now?: () => number } = {}):
506
+ { ready: true; expiresAt: string | null } | { ready: false; reason: 'missing' | 'expiring'; expiresAt: string | null } {
507
+ const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
508
+ let at: string | null = null;
509
+ try {
510
+ const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, { expires_at?: unknown }>;
511
+ at = Object.values(scopes).flatMap((s) => (s && typeof s.expires_at === 'string' ? [s.expires_at] : [])).sort().at(-1) ?? null;
512
+ } catch { /* no auth file */ }
513
+ if (credential?.kind !== 'subscription') return { ready: false, reason: 'missing', expiresAt: at };
514
+ const expiring = isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS, ...(opts.now ? { now: opts.now } : {}) });
515
+ return expiring ? { ready: false, reason: 'expiring', expiresAt: at } : { ready: true, expiresAt: at };
434
516
  }
435
517
 
436
518
  /** The one launch line for a skipped grok copy — the reader sees why grok left this run's chain. */
@@ -861,11 +943,17 @@ export function podDelegatedChildLlm(args: readonly string[]): { provider: strin
861
943
  * 짧게(≤8s) 백그라운드로 쏘고 `exit 143` 으로 원래 rc 를 보존한다 — podUsageMissingReason 의 `sigterm-143`
862
944
  * 분기가 실물 경로로 도달한다(시험은 합성 exitCode:143 이 아니라 실물 신호로 잰다).
863
945
  * ⛔ 롤업 실패는 rc 를 바꾸지 않는다(각 단계 `|| true`). trap 몸통은 짧게 유지한다(골 ①). */
864
- export function podSigtermTrapScript(): string {
865
- return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
946
+ /** METER-POD-ROLLUP-GAP-2 — 부분 롤업 표본기가 «방금 낸 seq» 를 적는 파일. trap 은 여기에 +1 해 마지막 PARTIAL 이 가장 큰 seq 를 갖게 한다
947
+ * (표본기는 서브셸이라 변수가 trap 에 안 보인다 · 없거나 숫자가 아니면 0 으로 보고 1 을 쓴다). */
948
+ export const POD_USAGE_PARTIAL_SEQ_FILE = '/tmp/si.usage-partial-seq';
949
+
950
+ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FILE): string {
951
+ // ⭐ METER-POD-ROLLUP-GAP-2 — trap PARTIAL 은 `--seq <표본기 seq + 1>` 로 낸다(전엔 seq 가 늘 1 이라 dedup 에서 앞선 표본에 졌다) ·
952
+ // 표본기를 먼저 끊어 trap 뒤에 옛 표본이 «마지막 줄»로 끼어들지 않게 한다.
953
+ return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
866
954
  }
867
955
 
868
- export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
956
+ export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
869
957
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
870
958
  const childProviderIndex = o.args.indexOf('--child-llm-provider');
871
959
  const childModelIndex = o.args.indexOf('--child-llm-model');
@@ -916,7 +1004,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
916
1004
  `(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
917
1005
  ...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
918
1006
  // METER-POD-ROLLUP-GAP — 부분 롤업 표본기: 최종 줄까지 못 사는 Pod 도 N 분마다(기본 5 · ELANOUS_USAGE_PARTIAL_MINUTES) PARTIAL 줄을 하나씩 남긴다.
919
- `(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
1007
+ `(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); printf '%s\\n' "$usage_partial_seq" > ${POD_USAGE_PARTIAL_SEQ_FILE} 2>/dev/null || true; [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
920
1008
  // ⭐ MF-d3b5a70f — si_pid 초기화 + trap: 메인 명령을 아래에서 `& si_pid=$!` 로 띄우고 `wait "$si_pid"` 로
921
1009
  // 기다려야 trap 이 grace 안에 발화한다(bash 는 foreground 명령 실행 중 trap 을 미룬다). trap 은
922
1010
  // podSigtermTrapScript() 가 내놓는다(수신 측 reemitPodUsage 포맷과 계약을 같은 파일 안에서 공유).
@@ -997,7 +1085,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
997
1085
  return {
998
1086
  apiVersion: 'batch/v1',
999
1087
  kind: 'Job',
1000
- metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
1088
+ metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
1001
1089
  ...((o.execution || o.hostLeaseAdmitted) ? { annotations: { ...(o.execution ? { [POD_EXECUTION_KEY_ANNOTATION]: o.execution.key, [POD_ATTEMPT_ANNOTATION]: String(o.execution.attempt) } : {}), ...(o.hostLeaseAdmitted ? { [POD_HOST_LEASE_ANNOTATION]: 'true' } : {}) } } : {}) },
1002
1090
  spec: {
1003
1091
  backoffLimit: 0,
@@ -1005,7 +1093,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1005
1093
  activeDeadlineSeconds: o.deadlineSeconds,
1006
1094
  template: {
1007
1095
  // ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
1008
- metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
1096
+ metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
1009
1097
  spec: {
1010
1098
  restartPolicy: 'Never',
1011
1099
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
@@ -1020,6 +1108,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1020
1108
  env: [
1021
1109
  ...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
1022
1110
  ...(o.parentRunId ? [{ name: 'ELANOUS_PARENT_RUN_ID', value: o.parentRunId }] : []),
1111
+ ...(o.workIdentity?.taskCard ? [{ name: 'ELANOUS_TASK_CARD_ID', value: o.workIdentity.taskCard }] : []),
1023
1112
  { name: 'ELANOUS_SUBSTRATE', value: 'pod' },
1024
1113
  ...Object.entries(controlInboxEnv(POD_CONTROL_INBOX_DIR)).map(([name, value]) => ({ name, value })),
1025
1114
  // ⭐ 런 출처(🅣 RFC run-origin §A3 · #20457/#20468 칸 이름) — Pod 는 자기 elanous_id 를 쓰지 않고 «띄운 감독기»의 hostId 를 물려받는다.
@@ -1705,11 +1794,20 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1705
1794
  ...(authorChoice.modelSource !== 'default' ? { ELANOUS_PROMPT_ENHANCE_MODEL: authorChoice.model } : {}),
1706
1795
  ...(authorChoice.effortSource !== 'default' ? { ELANOUS_PROMPT_ENHANCE_EFFORT: authorChoice.effort } : {}),
1707
1796
  } : {};
1708
- const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil || seat || env.ELANOUS_POD_AUTHOR_ON_POD === '1' || reviewerPin
1797
+ const budgetRead = hostBudgetSnapshot(podDelegatedChildLlm(jobArgs) ?? {}, options, env, name);
1798
+ const budgetSnapshot = budgetRead instanceof Promise ? await budgetRead : budgetRead;
1799
+ // REVIEW-ACP-POD ②-b — 호스트가 병합 전에 Claude ACP 로 리뷰할 런이면 Pod 안 api 리뷰를 «호스트로 미룬다»(리뷰 둘 = 돈 두 번 방지).
1800
+ // ⛔ 자동 병합(merge-by-host) 런만 — 호스트 리뷰 문은 merge-ready 에서만 열린다. kubectl 주입(=시험)이면 사용자 설정을 읽지 않는다.
1801
+ const reviewDeferredToHost = input.autoMerge === true
1802
+ && resolvePodHostAcpReview(options.hostAcpReview, env, options.kubectl ? () => undefined : getUserConfig).enabled;
1803
+ if (reviewDeferredToHost) debug.log('self-implement.pod', 'review-deferred-armed', { job: name, env: REVIEW_DEFERRED_TO_HOST_ENV, ...(reviewerPin ? { reviewerPinUnused: reviewerPin } : {}) });
1804
+ const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil || seat || env.ELANOUS_POD_AUTHOR_ON_POD === '1' || reviewerPin || reviewDeferredToHost || budgetSnapshot
1709
1805
  ? {
1710
1806
  ...armEnv,
1807
+ ...(budgetSnapshot ? { [BUDGET_SNAPSHOT_ENV]: encodeBudgetSnapshot(budgetSnapshot) } : {}),
1711
1808
  ...authorEnv,
1712
1809
  ...(reviewerPin ? { [roleLlmEnvName('review')]: reviewerPin } : {}),
1810
+ ...(reviewDeferredToHost ? { [REVIEW_DEFERRED_TO_HOST_ENV]: '1' } : {}),
1713
1811
  ...(detailUntil ? { ELANOUS_LIVE_DETAIL_UNTIL: detailUntil } : {}),
1714
1812
  ...(env.ELANOUS_DISPATCH_RECORDED === '1' ? { ELANOUS_DISPATCH_RECORDED: '1' } : {}),
1715
1813
  ...(seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX' ? { ELANOUS_HARNESS_SEAT: seat } : {}),
@@ -1732,7 +1830,16 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1732
1830
  debug.log('self-implement.pod', 'memory-request', { spaceId: input.spaceId, job: name,
1733
1831
  tier: oomRetried ? retryTier : memoryTier, reason: oomRetried ? 'OOMKilled' : memoryReason,
1734
1832
  memoryLimit, memoryRequest });
1735
- const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1833
+ const ownerKind = env.ELANOUS_POD_OWNER_KIND;
1834
+ const workIdentity: PodWorkIdentity = {
1835
+ ...(ownerKind === 'seat' || ownerKind === 'steward' || ownerKind === 'orchestrator' || ownerKind === 'external'
1836
+ ? { ownerKind, ownerId: env.ELANOUS_POD_OWNER_ID } : seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX'
1837
+ ? { ownerKind: 'seat', ownerId: seat } : {}),
1838
+ taskCard: env.ELANOUS_POD_TASK_CARD ?? env.ELANOUS_TASK_CARD_ID,
1839
+ slotId: env.ELANOUS_POD_SLOT_ID,
1840
+ work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
1841
+ };
1842
+ const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1736
1843
  const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
1737
1844
  const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
1738
1845
  const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
@@ -1793,6 +1900,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1793
1900
  let abortLogs = '';
1794
1901
  // ⭐ 런 «도중» 원장 증분 회수 — 호스트 슈퍼바이저가 Pod 걸음을 실시간으로 본다(🅣 요청 · 힐 루프 runtime 성형의 전제).
1795
1902
  const follower = createPodLedgerFollower({ runId: liveChildRunId, dir: ledgerDir, exec: (script) => kubectl(['-n', namespace, 'exec', `job/${name}`, '-c', 'child', '--', 'sh', '-c', script]) });
1903
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
1904
+ let livePartialLine: string | null = null;
1905
+ let pollsSinceCapture = 0;
1906
+ const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
1796
1907
  for (;;) {
1797
1908
  if (input.signal?.aborted) {
1798
1909
  // METER-POD-ROLLUP-GAP — Job 삭제 «전»에 로그를 한 번 거둔다: PARTIAL 줄이 삭제와 함께 사라지면 사용량이 0 처럼 보인다.
@@ -1852,6 +1963,18 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1852
1963
  }
1853
1964
  }
1854
1965
  try { follower?.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
1966
+ if (++pollsSinceCapture >= capturePolls) {
1967
+ pollsSinceCapture = 0;
1968
+ try {
1969
+ const sampled = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', `--since=${POD_USAGE_PARTIAL_CAPTURE_SINCE}`]);
1970
+ const seen = sampled.status === 0 ? lastPodUsagePartialLine(sampled.stdout) : null;
1971
+ // 창 안에 새 줄이 없으면 앞서 쥔 줄을 지킨다(덮어 지우지 않는다).
1972
+ if (seen) {
1973
+ livePartialLine = seen;
1974
+ debug.log('self-implement.pod', 'usage-partial-captured', { job: name, bytes: seen.length });
1975
+ }
1976
+ } catch (e) { debug.log('self-implement.pod', 'usage-partial-capture-failed', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
1977
+ }
1855
1978
  await sleep(options.pollMs ?? 15_000);
1856
1979
  }
1857
1980
  let logs = '';
@@ -1891,7 +2014,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1891
2014
  // ⭐ 호스트 단가로 다시 매긴다(BACKLOG C1b) — Pod 엔 레지스트리 스냅숏이 없다.
1892
2015
  const { estimateLlmCost } = await import('../../budget/llm-cost.js');
1893
2016
  // METER-POD-ROLLUP-GAP — 최종 줄 → 마지막 PARTIAL 폴백 → 결손 행(measured:false · 종료 사유): 사용량이 0 처럼 보이는 침묵을 남기지 않는다.
1894
- reemitPodUsage(logs, name, undefined, (u) => estimateLlmCost(u) as { kind: string; usd?: number }, { reason: podUsageMissingReason({ failedReason, exitCode: containerExitCode, logsAvailable: Boolean(logs) }), runId: liveChildRunId });
2017
+ // METER-POD-ROLLUP-GAP-3 — 판정은 kubectl 없는 순수 함수(podUsageReemitPlan)가 하고 여기선 행을 내보내기만 한다(ux-sim 심이 같은 함수를 부른다).
2018
+ const usagePlan = podUsageReemitPlan({ job: name, runId: liveChildRunId, logs, livePartialLine, exit: { state, failedReason, containerReason, containerExitCode } }, (u) => estimateLlmCost(u) as { kind: string; usd?: number });
2019
+ for (const row of usagePlan.rows) debug.log(row.category, row.event, row.data);
1895
2020
  const parsed = parseSelfImplementJson(logs);
1896
2021
  const terminal = parseSelfImplementJson(logs.trimEnd().split('\n').at(-1) ?? '');
1897
2022
  const nextAccount = codexAccounts?.find((candidate) => candidate !== account);
@@ -1936,7 +2061,38 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1936
2061
  if (!taLandOwner) debug.log('self-implement.pod', 'ta-land-marker-ignored', { job: name, pr: prNumber ?? null,
1937
2062
  reason: unreadable !== null ? 'live-moves-unreadable' : 'propose-land-not-enabled', ...(unreadable !== null ? { error: unreadable } : {}) });
1938
2063
  }
2064
+ // REVIEW-ACP-POD — opt-in: 재게이트 «전»에 호스트 Claude ACP(구독) 리뷰. must-fix 면 병합하지 않고 PR 을 연 채 둔다.
2065
+ // ⛔ 못 쓰면(refused) 종전 길 그대로 — Pod 리뷰 결과로 재게이트한다(다른 공급자로 새지 않는다 · 관측 `acp-review-refused`).
2066
+ // ⛔ kubectl 주입(=시험)이면 사용자 설정을 읽지 않는다.
2067
+ let hostAcpMustFix: Extract<PodHostAcpReviewOutcome, { status: 'must-fix' }> | undefined;
2068
+ // REVIEW-ACP-POD ②-b — Pod 가 리뷰를 «미뤘다»면(mergeReason) 호스트 ACP 리뷰가 «유일한» 리뷰다:
2069
+ // 스위치가 그 사이 꺼졌어도 리뷰한다 · 못 하면(refused) 병합하지 않는다(fail-closed — 리뷰 없이 병합 0).
2070
+ const podReviewDeferred = disposition.mergeReason === REVIEW_DEFERRED_TO_HOST_MERGE_REASON;
2071
+ let hostAcpUnavailable: string | undefined;
2072
+ const hostAcpSwitch = resolvePodHostAcpReview(options.hostAcpReview, env, options.kubectl ? () => undefined : getUserConfig);
2073
+ if ((hostAcpSwitch.enabled || podReviewDeferred) && Number.isSafeInteger(prNumber) && prNumber && headCommit && /^[0-9a-f]{40}$/i.test(headCommit)) {
2074
+ const hostRoot = findGitDir(process.cwd())?.root;
2075
+ const outcome: PodHostAcpReviewOutcome = hostRoot
2076
+ ? await (options.podHostAcpReview ?? ((i) => runPodHostAcpReview(i, { env })))({ prNumber, headCommit, repoRoot: hostRoot, job: name })
2077
+ : { status: 'refused', reason: 'host repository unavailable' };
2078
+ if (outcome.status === 'refused' && !hostRoot) debug.log('self-implement.pod', 'acp-review-refused', { job: name, pr: prNumber, reason: outcome.reason }, { level: 'warn' });
2079
+ debug.log('self-implement.pod', 'host-acp-review-gate', { job: name, pr: prNumber, source: hostAcpSwitch.enabled ? hostAcpSwitch.source : 'pod-deferred', podReviewDeferred, status: outcome.status, ...(outcome.status === 'refused' ? { reason: outcome.reason, path: podReviewDeferred ? 'hold-pr' : 'pod-review' } : { mustFix: outcome.mustFix }) });
2080
+ if (outcome.status === 'must-fix') hostAcpMustFix = outcome;
2081
+ if (outcome.status === 'refused' && podReviewDeferred) hostAcpUnavailable = outcome.reason;
2082
+ }
1939
2083
  let regate: HostRegateResult;
2084
+ if (hostAcpUnavailable !== undefined) {
2085
+ // ⛔ Pod 안 리뷰가 없다 — 재게이트·병합으로 가면 «리뷰 없이 병합»이다. PR 을 연 채 둔다.
2086
+ debug.log('self-implement.pod', 'host-acp-review-unavailable', { job: name, pr: prNumber ?? null, head: headCommit ?? null, reason: hostAcpUnavailable }, { level: 'warn' });
2087
+ try { (options.ghComment ?? defaultGhComment)(prNumber!, `호스트 Claude ACP 리뷰를 못 했다(${hostAcpUnavailable.replace(/\s+/g, ' ').slice(0, 200)}) — Pod 안 리뷰는 호스트로 미뤄져 없으므로 자동 병합하지 않는다(REVIEW-ACP-POD ②-b).`); }
2088
+ catch (error) { debug.log('self-implement.pod', 'host-acp-review-comment-failed', { job: name, pr: prNumber ?? null, detail: (error instanceof Error ? error.message : String(error)).slice(0, 200) }); }
2089
+ disposition = { ...disposition, stage: 'pr-opened', merged: false, ok: false, mergeReason: 'host-acp-review-unavailable' };
2090
+ } else if (hostAcpMustFix) {
2091
+ const body = [`호스트 Claude ACP 리뷰: must-fix ${hostAcpMustFix.mustFix}건 — 자동 병합하지 않는다(REVIEW-ACP-POD).`, ...hostAcpMustFix.findings.map((f, i) => `${i + 1}. ${f.replace(/\s+/g, ' ').slice(0, 400)}`)].join('\n');
2092
+ try { (options.ghComment ?? defaultGhComment)(prNumber!, body); }
2093
+ catch (error) { debug.log('self-implement.pod', 'host-acp-review-comment-failed', { job: name, pr: prNumber ?? null, detail: (error instanceof Error ? error.message : String(error)).slice(0, 200) }); }
2094
+ disposition = { ...disposition, stage: 'pr-opened', merged: false, ok: false, mergeReason: 'host-acp-review-must-fix' };
2095
+ } else {
1940
2096
  if (!Number.isSafeInteger(prNumber) || !prNumber || !headCommit || !/^[0-9a-f]{40}$/i.test(headCommit)) {
1941
2097
  debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
1942
2098
  regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
@@ -1969,6 +2125,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1969
2125
  if (taLandOwner) debug.log('self-implement.pod', 'ta-land-owner', { job: name, pr: prNumber ?? null, head: headCommit ?? null, regatePassed: false });
1970
2126
  disposition = { ...disposition, stage: frozen || releaseHold ? 'pr-opened' : regate.passed ? 'merged' : 'host-regate-failed', merged: regate.passed && !frozen, hostRegate: regate, ok: regate.passed || releaseHold || frozen };
1971
2127
  }
2128
+ }
1972
2129
  }
1973
2130
  const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
1974
2131
  ? lastPodChildFailure(logs) : null;
@@ -2220,18 +2377,34 @@ export function podUsageMissingReason(input: { logsAvailable: boolean; exitCode?
2220
2377
  return 'stopped';
2221
2378
  }
2222
2379
 
2223
- export function reemitPodUsage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d), reprice?: PodRowReprice, missing?: { reason: PodUsageMissingReason; runId?: string | null }): number {
2380
+ const POD_USAGE_PARTIAL_PREFIX = 'ELANOUS_USAGE_ROLLUP_PARTIAL ';
2381
+
2382
+ /** METER-POD-ROLLUP-GAP-3 — 로그 덩어리에서 «마지막» PARTIAL 줄(없으면 null). 런 도중 표본과 종료 로그가 같은 판정을 쓴다. */
2383
+ export function lastPodUsagePartialLine(logs: string): string | null {
2384
+ return logs.split('\n').reverse().find((l) => l.startsWith(POD_USAGE_PARTIAL_PREFIX)) ?? null;
2385
+ }
2386
+
2387
+ /** METER-POD-ROLLUP-GAP-3 — 호스트가 런 «도중» 거두는 PARTIAL 표본 간격(Pod 표본기 기본 5분과 맞춘다) ⊕ 그때 읽는 로그 창(표본 간격 둘 ⊕ 여유). */
2388
+ export const POD_USAGE_PARTIAL_CAPTURE_MS = 5 * 60_000;
2389
+ export const POD_USAGE_PARTIAL_CAPTURE_SINCE = '11m';
2390
+
2391
+ export function reemitPodUsage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d), reprice?: PodRowReprice, missing?: { reason: PodUsageMissingReason; runId?: string | null; podExitClass?: PodExitClass | null; livePartialLine?: string | null }): number {
2224
2392
  const lines = logs.split('\n').reverse();
2225
2393
  const line = lines.find((l) => l.startsWith('ELANOUS_USAGE_ROLLUP '));
2226
2394
  // ⭐ METER-POD-ROLLUP-GAP — 최종 줄이 없으면 마지막 PARTIAL 줄로 폴백한다(부분 롤업 표본기가 남긴 줄).
2227
- const partialLine = line ? null : lines.find((l) => l.startsWith('ELANOUS_USAGE_ROLLUP_PARTIAL '));
2395
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 종료 로그에도 PARTIAL 이 없으면(Pod 가 지워져 로그가 사라졌다) 런 «도중» 호스트가 거둔 마지막 PARTIAL 로.
2396
+ const logPartial = line ? null : lastPodUsagePartialLine(logs);
2397
+ const livePartial = line || logPartial || !missing?.livePartialLine?.startsWith(POD_USAGE_PARTIAL_PREFIX) ? null : missing.livePartialLine;
2398
+ const partialLine = logPartial ?? livePartial;
2228
2399
  if (!line && !partialLine) {
2229
- log('self-implement.pod', 'usage-rollup-missing', { job });
2400
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 결손 이벤트에도 종료 사유를 싣는다(옛 줄은 {job} 뿐이라 «왜 없나» 를 못 물었다).
2401
+ const why = missing ? { reason: missing.reason, ...(missing.podExitClass ? { podExitClass: missing.podExitClass } : {}), ...(missing.runId ? { runId: missing.runId } : {}) } : {};
2402
+ log('self-implement.pod', 'usage-rollup-missing', { job, ...why });
2230
2403
  // ⭐ 결손 행 — «측정 불가» 를 한 줄로 남긴다: 0 처럼 보이지 않게.
2231
- if (missing) log('llm.usage', 'llm-usage', { site: 'pod-rollup:missing', measured: false, reason: missing.reason, job, ...(missing.runId ? { runId: missing.runId } : {}), substrate: 'pod', podRollupKey: `${missing.runId ?? job}|missing` });
2404
+ if (missing) log('llm.usage', 'llm-usage', { site: 'pod-rollup:missing', measured: false, ...why, job, substrate: 'pod', podRollupKey: `${missing.runId ?? job}|missing` });
2232
2405
  return 0;
2233
2406
  }
2234
- const prefix = line ? 'ELANOUS_USAGE_ROLLUP ' : 'ELANOUS_USAGE_ROLLUP_PARTIAL ';
2407
+ const prefix = line ? 'ELANOUS_USAGE_ROLLUP ' : POD_USAGE_PARTIAL_PREFIX;
2235
2408
  let parsed: { measured?: boolean; truncated?: boolean; runId?: string | null; armId?: string | null; podName?: string | null; nodeName?: string | null; hostId?: string | null; seq?: number; at?: string; rows?: Array<Record<string, unknown>> };
2236
2409
  try { parsed = JSON.parse((line ?? partialLine)!.slice(prefix.length)); } catch { log('self-implement.pod', 'usage-rollup-unparsable', { job }); return 0; }
2237
2410
  const rows = parsed.rows ?? [];
@@ -2249,16 +2422,51 @@ export function reemitPodUsage(logs: string, job: string, log: (category: string
2249
2422
  substrate: 'pod', job, ...(parsed.runId ? { podRunId: parsed.runId } : {}), ...(parsed.armId ? { armId: parsed.armId } : {}),
2250
2423
  // ⭐ MF-3c0d253e — 재방출 행의 downstream dedup 키: 같은 Pod(job·runId)의 같은 버킷(site·provider·model)은
2251
2424
  // 부분(seq 달라도)·최종·abort⊕resume 재방출이 몇 번 만나도 집계(rollup)에서 한 번만 센다. 키 없는 기존 행은 종전대로.
2252
- podRollupKey: `${job}|${parsed.runId ?? ''}|pod-rollup:${String(r.site)}|${String(r.provider)}|${String(r.model)}`,
2425
+ // ⭐ METER-POD-ROLLUP-GAP-2 — podName 을 키에 넣는다: gh-login·usage-limit 재시도는 «같은 job 이름·runId» 로 새 Pod 를 띄우므로
2426
+ // (podJobName 은 spaceId 결정적) 그것 없이는 서로 다른 시도의 사용량이 한 번으로 접혀 앞 시도분이 사라진다.
2427
+ podRollupKey: `${job}|${parsed.podName ?? ''}|${parsed.runId ?? ''}|pod-rollup:${String(r.site)}|${String(r.provider)}|${String(r.model)}`,
2253
2428
  ...(parsed.podName ? { podName: parsed.podName } : {}), ...(parsed.nodeName ? { nodeName: parsed.nodeName } : {}), ...(parsed.hostId ? { podHostId: parsed.hostId } : {}),
2254
2429
  // ⭐ PARTIAL 폴백 재방출 꼬리표 — 최종 줄 행은 그대로(부분이 아님을 꾸미지 않는다).
2255
- ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { partialSeq: parsed.seq } : {}), ...(parsed.at !== undefined ? { partialAt: parsed.at } : {}) } : {}),
2430
+ ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { partialSeq: parsed.seq } : {}), ...(parsed.at !== undefined ? { partialAt: parsed.at } : {}), ...(livePartial ? { partialSource: 'live-capture' } : {}) } : {}),
2256
2431
  });
2257
2432
  }
2258
- log('self-implement.pod', 'usage-rollup', { job, rows: rows.length, measured: parsed.measured ?? null, truncated: parsed.truncated ?? null, ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { seq: parsed.seq } : {}), ...(parsed.at !== undefined ? { at: parsed.at } : {}) } : {}) });
2433
+ log('self-implement.pod', 'usage-rollup', { job, rows: rows.length, measured: parsed.measured ?? null, truncated: parsed.truncated ?? null, ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { seq: parsed.seq } : {}), ...(parsed.at !== undefined ? { at: parsed.at } : {}), ...(livePartial ? { partialSource: 'live-capture' } : {}) } : {}) });
2259
2434
  return rows.length;
2260
2435
  }
2261
2436
 
2437
+ /** METER-POD-ROLLUP-GAP-3 — 호스트 사용량 재방출 판정의 «순수» 입력·출력(kubectl·debug 없음). */
2438
+ export interface PodUsageReemitInput {
2439
+ job: string;
2440
+ runId?: string | null;
2441
+ /** Job 이 끝난 뒤 거둔 로그(Pod 가 사라졌으면 ''). */
2442
+ logs: string;
2443
+ /** 런 «도중» 호스트가 본 마지막 `ELANOUS_USAGE_ROLLUP_PARTIAL` 줄(없으면 null). */
2444
+ livePartialLine?: string | null;
2445
+ /** 종료 정보 — state 가 'failed' 일 때만 classifyPodExit 분류를 싣는다. */
2446
+ exit: { state: 'complete' | 'failed' | 'aborted'; failedReason?: string | null; containerReason?: string | null; containerExitCode?: number | null };
2447
+ }
2448
+ export interface PodUsageReemitPlan {
2449
+ /** 어느 줄로 재방출했나 — final(최종 줄) · partial-log(끝 로그의 PARTIAL) · partial-live(런 도중 본 PARTIAL) · missing(결손 행) · unparsable. */
2450
+ source: 'final' | 'partial-log' | 'partial-live' | 'missing' | 'unparsable';
2451
+ /** 그대로 debug.log(category, event, data) 로 내보낼 행들(llm-usage 행 ⊕ self-implement.pod 요약/결손 이벤트). */
2452
+ rows: Array<{ category: string; event: string; data: Record<string, unknown> }>;
2453
+ }
2454
+
2455
+ /** METER-POD-ROLLUP-GAP-3 — 끝 로그 ⊕ 런 도중 PARTIAL ⊕ 종료 정보 → 재방출할 행/결손 행. 호스트 Job 경로와 ux-sim 심이 «같은» 함수를 부른다. */
2456
+ export function podUsageReemitPlan(input: PodUsageReemitInput, reprice?: PodRowReprice): PodUsageReemitPlan {
2457
+ const { job, logs, exit } = input;
2458
+ const reason = podUsageMissingReason({ failedReason: exit.failedReason ?? undefined, exitCode: exit.containerExitCode ?? null, logsAvailable: Boolean(logs) });
2459
+ const podExitClass = exit.state === 'failed' ? classifyPodExit({ containerReason: exit.containerReason ?? null, containerExitCode: exit.containerExitCode ?? null, jobReason: exit.failedReason || null, logs }).class : null;
2460
+ const rows: PodUsageReemitPlan['rows'] = [];
2461
+ reemitPodUsage(logs, job, (category, event, data) => rows.push({ category, event, data }), reprice, { reason, runId: input.runId ?? null, podExitClass, livePartialLine: input.livePartialLine ?? null });
2462
+ const usage = rows.find((r) => r.category === 'llm.usage');
2463
+ const source: PodUsageReemitPlan['source'] = rows.some((r) => r.event === 'usage-rollup-missing') ? 'missing'
2464
+ : rows.some((r) => r.event === 'usage-rollup-unparsable') ? 'unparsable'
2465
+ : rows.find((r) => r.event === 'usage-rollup')?.data.partialSource === 'live-capture' ? 'partial-live'
2466
+ : rows.find((r) => r.event === 'usage-rollup')?.data.partial === true || usage?.data.partial === true ? 'partial-log' : 'final';
2467
+ return { source, rows };
2468
+ }
2469
+
2262
2470
  /** 이 기계가 Pod 칸을 쓸 수 있나(클러스터 · 이미지) — 발사 전 한 번. */
2263
2471
  export function podSubstrateReady(kubectl: Kubectl = defaultKubectl, image = 'elanous-harness:local'): { ok: boolean; reason: string } {
2264
2472
  const ctx = kubectl(['config', 'current-context']);
@@ -29,6 +29,31 @@ export interface ToolSearchRunResult {
29
29
  [HYDRATED_TOOLS_KEY]?: LLMToolSpec[];
30
30
  }
31
31
 
32
+ /** Session runtime applies this only to the fixed harness turn, using its
33
+ * originating userText. Both the callable catalog and visible schemas must
34
+ * agree, so a later model turn cannot rehydrate Edit or Write from this result. */
35
+ export function blockWriteToolSearchHydration(result: ToolSearchRunResult): ToolSearchRunResult {
36
+ const hydrated = result[HYDRATED_TOOLS_KEY] ?? [];
37
+ const allowed = hydrated.filter((tool) => tool.name !== 'Edit' && tool.name !== 'Write');
38
+ if (allowed.length === hydrated.length) return result;
39
+ const blocked = new Set(['Edit', 'Write']);
40
+ const matched = result.matched.filter((name) => !blocked.has(name));
41
+ const schemas = allowed.length > 0
42
+ ? `<functions>\n${allowed.map((tool) => `<function>${JSON.stringify({
43
+ description: tool.description,
44
+ name: tool.name,
45
+ parameters: tool.parameters,
46
+ })}</function>`).join('\n')}\n</functions>`
47
+ : '';
48
+ const output = `# ToolSearch: ${matched.length} allowed match${matched.length === 1 ? '' : 'es'}\n\n${schemas}`;
49
+ return {
50
+ ...result,
51
+ output,
52
+ matched,
53
+ [HYDRATED_TOOLS_KEY]: allowed,
54
+ };
55
+ }
56
+
32
57
  export const toolSearchRuntime: ToolRuntime<ToolSearchArgs, ToolSearchRunResult> = {
33
58
  id: 'tool_search',
34
59
  spec: buildToolSearchTool(),