elanous 0.2.34 → 0.2.36

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 (802) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → 053a429e.9a63436527cc751c.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +287 -0
  5. package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +64 -0
  11. package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +36 -0
  12. package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +149 -0
  15. package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 2683.c9773a0c589b031b.js} +4 -4
  20. package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
  21. package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 275-02ea4c2a33980422.js} +3 -3
  22. package/apps/pwa/out/_next/static/chunks/{ffdc3a04.b9c23c1b28dd4a96.js → 2812ca6a.3b9f4a7a08885744.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
  26. package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 2986.45d7652e4f13557e.js} +2 -2
  27. package/apps/pwa/out/_next/static/chunks/3031-85328a92fcce4da6.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{8817.61bb7ffbba502ecb.js → 3136.30aaaa5fa03662a3.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3148-98efd4869d660a87.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/{3498.cf2a15a8a9e5b926.js → 3155.2587a55196ecb394.js} +3 -3
  31. package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/{3103.84470c8445962f04.js → 3324.a09703033abcbd2f.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +8 -0
  36. package/apps/pwa/out/_next/static/chunks/3410.7f9af5ab77cbe65f.js +115 -0
  37. package/apps/pwa/out/_next/static/chunks/{3819.163d4777f0a597f6.js → 3538.a445e50bb359d068.js} +2 -2
  38. package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +286 -0
  40. package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 3727.7949ccaacaa71242.js} +2 -2
  42. package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
  45. package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → 3a3d9966.cc4d4585a2708e7c.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4116.a84a92a2492c080e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +104 -0
  51. package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 4286.b70c4520306817e8.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/4304.e9b2512834a9e23e.js +23 -0
  54. package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 4615.8525eb0add35eba6.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +148 -0
  57. package/apps/pwa/out/_next/static/chunks/{3411-f87314c6c66ebc73.js → 5085-bce4904931767ca6.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +93 -0
  59. package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 528.38ce20ef8aa251ff.js} +16 -16
  61. package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +122 -0
  62. package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 552.b7d061ad91eeac51.js} +2 -2
  63. package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{3837.722e053fb9f321ad.js → 5711.957b095705c12560.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 575.d929793a53f27c4b.js} +1 -1
  66. package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{7562.948255cbc3e79d33.js → 599.c39c571b6733f2a2.js} +2 -2
  70. package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +79 -0
  71. package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/6566.926a1d1b7c911442.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +27 -0
  84. package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/{5446.edf141cff9ef647d.js → 6925.562d532110879886.js} +1 -1
  86. package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 6943.c6b24124853a47b2.js} +1 -1
  87. package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 7481.39f7a4654907bf6e.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 771.c3cdb6c1f7f47c02.js} +16 -16
  92. package/apps/pwa/out/_next/static/chunks/7784.1f0ac4a16d190cf4.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 7891.03f910124a737985.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8037.e254fd797aa224d1.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +62 -0
  100. package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → 873d6c12.09bd499f57171543.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 8904.7d51af2dc033965c.js} +2 -2
  102. package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +90 -0
  104. package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +94 -0
  105. package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 9250-072df22eaadaa6b5.js} +1 -1
  107. package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +77 -0
  111. package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 9514.131c52cc75fe6439.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9f31e478.fefcbcd3c388ebc5.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/{page-4fb31eb476dcbad3.js → page-083747f1d4518f61.js} +1 -1
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e7abc34a48a6f71e.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-c15958835534404b.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → ceba0382.650712b56b85b582.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/da5ecc5c-b005ad8cac737d57.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +1 -0
  183. package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_buildManifest.js +1 -1
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +22 -22
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +22 -22
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +22 -22
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +21 -21
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +21 -21
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +22 -22
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +21 -21
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +22 -22
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +22 -22
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +21 -21
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +22 -22
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +22 -22
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +22 -22
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +22 -22
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +21 -21
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +21 -21
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +22 -22
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +22 -22
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +22 -22
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +21 -21
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +21 -21
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +22 -22
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +22 -22
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +21 -21
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +21 -21
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +21 -21
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +22 -22
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +22 -22
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +21 -21
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +22 -22
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +22 -22
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +22 -22
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +22 -22
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +22 -22
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +21 -21
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +22 -22
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +22 -22
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +22 -22
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +22 -22
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +22 -22
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +22 -22
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +22 -22
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +22 -22
  277. package/bin/elanous.mjs +3 -2
  278. package/catalog/providers/opengateway.yaml +25 -0
  279. package/docs/goal-context/01-goal-authoring-checks.md +8 -0
  280. package/docs/goal-context/03-verification-menu.md +24 -0
  281. package/docs/goal-context/04-mock-scenarios.md +100 -0
  282. package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
  283. package/docs/goal-context/README.md +7 -0
  284. package/graphs/catalog/catalog.yaml +1 -1
  285. package/graphs/default-loop.yaml +63 -4
  286. package/graphs/document-loop.yaml +51 -3
  287. package/graphs/implement-loop.yaml +77 -4
  288. package/graphs/journey/ta-journey.yaml +11 -1
  289. package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
  290. package/graphs/journey/units/author-dialogue/observe.ts +96 -0
  291. package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
  292. package/graphs/operate-loop.yaml +51 -3
  293. package/graphs/release/recipes.yaml +6 -0
  294. package/graphs/release/release-loop.yaml +17 -4
  295. package/graphs/research-loop.yaml +137 -3
  296. package/graphs/test-diet/test-diet.yaml +3 -0
  297. package/graphs/wizard/recipes.yaml +44 -0
  298. package/graphs/wizard/wizard-loop.yaml +44 -0
  299. package/graphs/work/research-report.yaml +2 -0
  300. package/package.json +1 -1
  301. package/scripts/ci-typecheck-changed.ts +139 -5
  302. package/scripts/coord-role-pack.ts +19 -6
  303. package/scripts/first10-gate.ts +416 -0
  304. package/scripts/fleet/wsl-node-setup.sh +432 -0
  305. package/scripts/hq/standby-snapshot.ts +17 -12
  306. package/scripts/release-loop/docs-land-node.ts +21 -5
  307. package/scripts/release-loop/docs-node.ts +204 -8
  308. package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
  309. package/scripts/release-loop/gate-node.ts +427 -72
  310. package/scripts/release-loop/gate-partial.ts +65 -11
  311. package/scripts/release-loop/gate-radius-live.ts +177 -0
  312. package/scripts/release-loop/gate-radius-shadow.ts +1 -1
  313. package/scripts/release-loop/node-verdict.ts +14 -1
  314. package/scripts/release-loop/npm-publish-node.ts +66 -12
  315. package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
  316. package/scripts/release-loop/pwa-remote.ts +166 -0
  317. package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
  318. package/scripts/release-loop/resume-cap.ts +14 -34
  319. package/scripts/release-loop/resume-release.ts +78 -15
  320. package/scripts/release-loop/traffic-node.ts +59 -0
  321. package/scripts/release-loop/traffic-transition.ts +159 -0
  322. package/scripts/release-loop/unattended-release.ts +2 -0
  323. package/scripts/rhythm/daily.ts +137 -3
  324. package/scripts/test-diet/lib.ts +6 -1
  325. package/scripts/test-diet/node.ts +194 -13
  326. package/scripts/ux-sim.ts +92 -0
  327. package/skills/README.md +1 -0
  328. package/src/acp/core-turn-bridge.ts +48 -6
  329. package/src/agent/delegate-hint.ts +16 -0
  330. package/src/agent/runner.ts +82 -2
  331. package/src/agent/self-ambient-cache.ts +8 -0
  332. package/src/agent/task-notification.ts +56 -10
  333. package/src/agent/types.ts +4 -0
  334. package/src/agent-substrate/acp-reviewer.ts +39 -4
  335. package/src/agent-substrate/pr-reviewer.ts +72 -3
  336. package/src/agent-substrate/review-intent.ts +29 -2
  337. package/src/agent-substrate/review-observation.ts +3 -0
  338. package/src/agent-substrate/self-review-cli.ts +229 -28
  339. package/src/autopilot/mission-codebase-gate.ts +141 -12
  340. package/src/autopilot/pr-manager.ts +240 -41
  341. package/src/boot/daemon-tools/self-implement.ts +1 -0
  342. package/src/budget/run-usage-rollup.ts +1 -0
  343. package/src/budget/usage-outcomes.ts +8 -4
  344. package/src/capture/tui-self-report.ts +102 -0
  345. package/src/chat/goal-continue-verdict.ts +41 -0
  346. package/src/chat/index.ts +30 -6
  347. package/src/chat/internal-turn-origin.ts +1 -0
  348. package/src/chat/paste-fold.ts +86 -0
  349. package/src/chat/pickers/modal-runtime.ts +10 -27
  350. package/src/chat/tool-render/block.ts +32 -0
  351. package/src/chat/tool-render/dispatch.ts +2 -20
  352. package/src/chat/tool-render/result-text.ts +98 -0
  353. package/src/chat/turn-typeahead.ts +70 -7
  354. package/src/cli/agent-cli.ts +13 -2
  355. package/src/cli/config-audit.ts +119 -0
  356. package/src/cli/doctor-cli.ts +26 -1
  357. package/src/cli/guardian-cli.ts +10 -0
  358. package/src/cli/herdr-cli.ts +34 -0
  359. package/src/cli/light-entry-route.ts +50 -7
  360. package/src/cli/log-event-names.ts +2 -0
  361. package/src/cli/make-cli.ts +247 -11
  362. package/src/cli/msg-cli.ts +117 -3
  363. package/src/cli/pod-cli.ts +77 -3
  364. package/src/cli/pr-cli.ts +34 -18
  365. package/src/cli/pr-land-base-read.ts +11 -2
  366. package/src/cli/pr-land-heavy-remote.ts +6 -3
  367. package/src/cli/pr-land-overlap-evidence.ts +23 -4
  368. package/src/cli/pr-land-pod-shadow-job.ts +3 -0
  369. package/src/cli/pr-land-recovery.ts +117 -1
  370. package/src/cli/pty-takeover-cli.ts +8 -7
  371. package/src/cli/release-checklist-read-cli.ts +79 -0
  372. package/src/cli/release-cli.ts +92 -106
  373. package/src/cli/release-single-flight.ts +1 -1
  374. package/src/cli/remote-resolve.ts +1 -1
  375. package/src/cli/root-tui-entry.ts +48 -0
  376. package/src/cli/sim-cli.ts +22 -0
  377. package/src/cli/tasks-card-show.ts +162 -0
  378. package/src/cli/tasks-cli.ts +1065 -338
  379. package/src/cli/tasks-read-cli.ts +197 -0
  380. package/src/cli/usage-cli.ts +13 -11
  381. package/src/clipboard/history.ts +179 -13
  382. package/src/config.ts +8 -1
  383. package/src/core-turn/run-core-turn.ts +4 -2
  384. package/src/core-turn/run-goal-loop.ts +73 -16
  385. package/src/core-turn/types.ts +6 -1
  386. package/src/dashboard/chat/acp-chat.ts +8 -0
  387. package/src/dashboard/context/keys.ts +8 -31
  388. package/src/dashboard/index.ts +280 -59
  389. package/src/dashboard/input/agent-wake.ts +7 -1
  390. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
  391. package/src/dashboard/input/chat-main-slash-command.ts +4 -2
  392. package/src/dashboard/input/goal-continue.ts +210 -0
  393. package/src/dashboard/input/goal-interim.ts +2 -0
  394. package/src/dashboard/input/internal-turn.ts +2 -8
  395. package/src/dashboard/input/seat-autowake.ts +9 -7
  396. package/src/dashboard/input/turn-autoheal.ts +52 -0
  397. package/src/dashboard/render/theme-resolver.ts +33 -4
  398. package/src/dashboard/seat-boot-preamble.ts +132 -5
  399. package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
  400. package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
  401. package/src/dashboard/slash-runtime/model-choices.ts +15 -0
  402. package/src/dashboard/stale-install-check.ts +3 -99
  403. package/src/debug/batch-queue.ts +119 -0
  404. package/src/debug/tui-log-tag.ts +67 -0
  405. package/src/display/agent-surface.ts +7 -0
  406. package/src/domains/schedule-registry.ts +16 -2
  407. package/src/domains/standalone-log-sink.ts +15 -2
  408. package/src/esc-abort-gate.ts +69 -0
  409. package/src/git-fs/idle-refresh.ts +76 -0
  410. package/src/git-fs/index.ts +18 -7
  411. package/src/git-fs/worktree.ts +137 -5
  412. package/src/graph-kinds/registry.ts +2 -2
  413. package/src/graph-runner/graph-cli.ts +18 -0
  414. package/src/graph-runner/runner.ts +173 -21
  415. package/src/graph-wizard/generate.ts +108 -4
  416. package/src/graph-wizard/steps.ts +15 -0
  417. package/src/grok/credential.ts +10 -3
  418. package/src/grounding/proactive-ground.ts +86 -0
  419. package/src/harness/control-inbox.ts +49 -1
  420. package/src/harness/harness-cli-command.ts +163 -35
  421. package/src/harness/harness-incidents.ts +85 -1
  422. package/src/harness/launch-utterance-ledger.ts +208 -0
  423. package/src/harness/pod-control-send.ts +33 -5
  424. package/src/harness/self-send-remote.ts +69 -0
  425. package/src/harness/self-send-routing-guard.ts +6 -0
  426. package/src/harness/self-send-target.ts +4 -3
  427. package/src/impl-discipline/detector.ts +16 -4
  428. package/src/impl-discipline/system-prompt.ts +11 -0
  429. package/src/impl-discipline/types.ts +2 -2
  430. package/src/index.ts +822 -154
  431. package/src/input-core/context-key-runtime.ts +21 -0
  432. package/src/intake-plane/already-built-hint.ts +4 -2
  433. package/src/intake-plane/requirement-funnel.ts +29 -1
  434. package/src/light-cli.ts +32 -11
  435. package/src/llm/opengateway-models.ts +11 -0
  436. package/src/llm/provider-credentials.ts +14 -0
  437. package/src/llm/provider-detect.ts +8 -1
  438. package/src/llm/runtime-model-family.ts +2 -1
  439. package/src/llm-wellformed.ts +70 -0
  440. package/src/llm.ts +247 -59
  441. package/src/loops/msg-wake.ts +35 -39
  442. package/src/loops/orchestrator/inbox.ts +59 -11
  443. package/src/loops/orchestrator/knowhow-pack.ts +364 -0
  444. package/src/loops/orchestrator/tick.ts +5 -1
  445. package/src/maturity/feature-maturity.ts +1 -1
  446. package/src/model-tier/llm-tier-map.ts +15 -1
  447. package/src/models/prompts.ts +17 -2
  448. package/src/msg/msg-post-office.ts +116 -0
  449. package/src/mss/logging/log-store.ts +79 -22
  450. package/src/native-tool-catalog.ts +15 -1
  451. package/src/nexus/api/decisions-route.ts +38 -18
  452. package/src/nexus/api/graph-wizard-api.ts +20 -2
  453. package/src/nexus/api/loop-resources.ts +13 -1
  454. package/src/nexus/boot/register-mcp-clients.ts +263 -38
  455. package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
  456. package/src/oauth/codex.ts +7 -0
  457. package/src/onboarding/first-run.ts +37 -6
  458. package/src/onboarding/web-first.ts +43 -11
  459. package/src/ops/overview-collect.ts +30 -9
  460. package/src/ops/overview.ts +22 -9
  461. package/src/persona/loader.ts +11 -0
  462. package/src/persona/types.ts +8 -0
  463. package/src/pr-tool/census.ts +261 -27
  464. package/src/prompt-library/session-guidance.ts +14 -0
  465. package/src/provider-summary.ts +8 -0
  466. package/src/pty-shell/herdr-agent-report.ts +118 -9
  467. package/src/pty-shell/herdr-prompt.ts +266 -0
  468. package/src/pty-shell/pty-control-ipc.ts +151 -8
  469. package/src/pty-shell/pty-manifest.ts +7 -0
  470. package/src/pty-shell/pty-special-keys.ts +42 -2
  471. package/src/pty-shell/stale-install-check.ts +99 -0
  472. package/src/pty-shell/tui-run-state.ts +14 -2
  473. package/src/release-loop/auto-start.ts +20 -2
  474. package/src/release-loop/checklist.ts +100 -4
  475. package/src/release-loop/landed-but-yellow.ts +129 -5
  476. package/src/release-loop/light-rc.ts +1 -0
  477. package/src/release-loop/rehearsal.ts +37 -21
  478. package/src/repl/index.ts +192 -17
  479. package/src/repl/ops-msg-bridge.ts +32 -0
  480. package/src/seat-loop/knowhow-lint.ts +1 -1
  481. package/src/self-dev/ask-launch-flow.ts +44 -5
  482. package/src/self-dev/cli-help-text.ts +17 -0
  483. package/src/self-dev/decompose-proposal.ts +172 -20
  484. package/src/self-dev/decompose.ts +61 -0
  485. package/src/self-dev/dev-cli.ts +48 -12
  486. package/src/self-dev/dev-pipeline.ts +8 -2
  487. package/src/self-dev/draft-residue.ts +37 -1
  488. package/src/self-dev/draft-sweep.ts +137 -11
  489. package/src/self-dev/draft-triage-rules.ts +138 -0
  490. package/src/self-dev/fleet-reduce.ts +3 -2
  491. package/src/self-dev/glm-credential-guard.ts +77 -0
  492. package/src/self-dev/graph-front-nodes.ts +112 -0
  493. package/src/self-dev/graph-journey-nodes.ts +2 -0
  494. package/src/self-dev/graph-journey-route.ts +12 -2
  495. package/src/self-dev/harness-run-cli.ts +3 -15
  496. package/src/self-dev/orchestrate-cli-text.ts +10 -0
  497. package/src/self-dev/orchestrate-cli.ts +68 -3
  498. package/src/self-dev/orchestrate.ts +116 -17
  499. package/src/self-dev/review-provider-fallback.ts +35 -1
  500. package/src/self-dev/run-supervisor.ts +55 -2
  501. package/src/self-dev/run-types.ts +1 -1
  502. package/src/self-dev/self-orchestrate-runtime.ts +1 -11
  503. package/src/self-dev/shard-aggregate.ts +42 -5
  504. package/src/self-dev/shard-run.ts +8 -1
  505. package/src/self-health/autoheal.ts +153 -0
  506. package/src/self-health/signatures.ts +6 -0
  507. package/src/self-implement/blocked-draft-policy.ts +20 -0
  508. package/src/self-implement/classify-shadow.ts +3 -7
  509. package/src/self-implement/device-first.ts +116 -0
  510. package/src/self-implement/doctor-visit.ts +257 -2
  511. package/src/self-implement/evidence-command-budget.ts +80 -0
  512. package/src/self-implement/gate-baseline.ts +56 -17
  513. package/src/self-implement/gate-cli.ts +1 -0
  514. package/src/self-implement/gate-remote.ts +31 -20
  515. package/src/self-implement/goal-author-cli.ts +1 -0
  516. package/src/self-implement/goal-author-decompose.ts +534 -45
  517. package/src/self-implement/goal-author.ts +282 -7
  518. package/src/self-implement/goal-elaborate.ts +449 -0
  519. package/src/self-implement/goal-refine.ts +273 -0
  520. package/src/self-implement/graph-overlay-yaml.ts +14 -3
  521. package/src/self-implement/graph-variant.ts +40 -6
  522. package/src/self-implement/headless-elanous-driver.ts +19 -3
  523. package/src/self-implement/off-diff-evidence.ts +58 -6
  524. package/src/self-implement/orchestrator.ts +370 -62
  525. package/src/self-implement/pod-warm-pool-profile.ts +1 -0
  526. package/src/self-implement/pwa-import-graph.ts +1 -1
  527. package/src/self-implement/rework-budget-judgment.ts +5 -8
  528. package/src/self-implement/rework-policy.ts +30 -0
  529. package/src/self-implement/run-ledger.ts +108 -0
  530. package/src/self-implement/run-stop.ts +10 -1
  531. package/src/self-implement/run-ttl.ts +38 -24
  532. package/src/self-implement/running-runs.ts +29 -1
  533. package/src/self-implement/scope-adjacency.ts +115 -0
  534. package/src/self-implement/seams.ts +187 -28
  535. package/src/self-implement/stop-autoheal.ts +87 -1
  536. package/src/self-implement/wiring-check.ts +358 -0
  537. package/src/session-runtime/index.ts +1 -1
  538. package/src/setup/llm-env-detect.ts +5 -0
  539. package/src/shell-env-bootstrap.ts +11 -4
  540. package/src/skills/runner.ts +4 -2
  541. package/src/skills/tools/agent.ts +408 -40
  542. package/src/skills/tools/msg-ask.ts +100 -0
  543. package/src/skills/tools/ops-ledger.ts +2 -1
  544. package/src/skills/tools/schedule-wakeup.ts +23 -4
  545. package/src/skills/tools/shell.ts +61 -6
  546. package/src/skills/tools/terminal-modal-inject.ts +1 -2
  547. package/src/skills/tools/terminal-modal.ts +10 -8
  548. package/src/skills/url-router.ts +41 -1
  549. package/src/smart-wizard/run.ts +66 -0
  550. package/src/status/bar.ts +34 -6
  551. package/src/status/status-provider-info.ts +37 -6
  552. package/src/steward/hold-hygiene.ts +24 -6
  553. package/src/steward/rejudge-tick.ts +10 -2
  554. package/src/steward/ta-repair-event-collect.ts +17 -6
  555. package/src/steward/triage-budget.ts +194 -0
  556. package/src/steward/triage.ts +20 -3
  557. package/src/task-agent/actions.ts +72 -2
  558. package/src/task-agent/green-sweep.ts +4 -0
  559. package/src/task-agent/land-queue.ts +21 -8
  560. package/src/task-agent/live-moves.ts +182 -10
  561. package/src/task-agent/mission.ts +30 -0
  562. package/src/task-agent/parent-host.ts +123 -3
  563. package/src/task-agent/rejudge-on-head.ts +15 -0
  564. package/src/task-agent/repair-cycle.ts +138 -32
  565. package/src/task-agent/repair-next-move.ts +31 -3
  566. package/src/task-agent/repair-outcomes.ts +29 -0
  567. package/src/task-agent/repair-worker.ts +14 -4
  568. package/src/task-agent/shadow.ts +48 -4
  569. package/src/task-agent/task-card-prefix.ts +2 -0
  570. package/src/task-agent/task-hand.ts +525 -57
  571. package/src/task-agent/task-parent-role-llm.ts +36 -0
  572. package/src/task-agent/tier-split.ts +196 -0
  573. package/src/task-agent/work-order-build.ts +2 -0
  574. package/src/task-agent/work-order.ts +3 -2
  575. package/src/task-orchestrator/generator-prompt.ts +60 -11
  576. package/src/task-orchestrator/surfaces/pod-account-broker.ts +42 -0
  577. package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
  578. package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
  579. package/src/task-orchestrator/surfaces/pod-command-job.ts +130 -11
  580. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
  581. package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
  582. package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
  583. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
  584. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +19 -2
  585. package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
  586. package/src/task-orchestrator/surfaces/pod-provenance.ts +82 -0
  587. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
  588. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
  589. package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
  590. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
  591. package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
  592. package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
  593. package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
  594. package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
  595. package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
  596. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
  597. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +7 -0
  598. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +521 -40
  599. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
  600. package/src/task-orchestrator/surfaces/self-implement-pod.ts +759 -50
  601. package/src/task-orchestrator/surfaces/self-implement.ts +65 -2
  602. package/src/tool-runtime/index.ts +15 -2
  603. package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
  604. package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
  605. package/src/tty-foreground.ts +155 -0
  606. package/src/tui/README.md +2 -0
  607. package/src/tui/chat/background-job-status.ts +54 -0
  608. package/src/tui/chat/goal-interim.ts +173 -0
  609. package/src/tui/chat/log-view.ts +267 -0
  610. package/src/tui/chat/self-health-status.ts +232 -0
  611. package/src/tui/entry.ts +695 -0
  612. package/src/tui/input/composer.ts +151 -0
  613. package/src/tui/seat-mail-boot.ts +59 -0
  614. package/src/tui/slash/compact-command.ts +7 -0
  615. package/src/tui/slash/compact.ts +94 -0
  616. package/src/tui/slash/new-session.ts +35 -0
  617. package/src/tui/slash/resume.ts +75 -0
  618. package/src/tui-client/local-chat-turn.ts +282 -0
  619. package/src/tui-client/local-harness-command.ts +63 -0
  620. package/src/tui-client/local-provider-command.ts +184 -0
  621. package/src/tui-client/next-dashboard-runtime.ts +8 -0
  622. package/src/tui-client/next-loop-command.ts +70 -0
  623. package/src/tui-client/next-wakeup.ts +191 -0
  624. package/src/tui.ts +37 -0
  625. package/src/ui/modal-adapter.ts +2 -2
  626. package/src/user-config.ts +4 -2
  627. package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
  628. package/src/ux-sim/cli-config-audit-device.ts +121 -0
  629. package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
  630. package/src/ux-sim/event-monkey.ts +379 -0
  631. package/src/ux-sim/fake-bin.ts +87 -7
  632. package/src/ux-sim/fake-herdr.ts +238 -0
  633. package/src/ux-sim/fake-llm.ts +111 -6
  634. package/src/ux-sim/fault-glm-no-credential.ts +116 -0
  635. package/src/ux-sim/fault-nextmove-dispatch.ts +367 -0
  636. package/src/ux-sim/fault-pod-codex-503.ts +211 -0
  637. package/src/ux-sim/fault-scope-escape-lift.ts +216 -0
  638. package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
  639. package/src/ux-sim/goal-device.ts +619 -0
  640. package/src/ux-sim/in-process-cli.ts +22 -5
  641. package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
  642. package/src/ux-sim/mock-authoring-first-run.ts +97 -0
  643. package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
  644. package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
  645. package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
  646. package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
  647. package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
  648. package/src/ux-sim/onboarding-first-run-device.ts +269 -0
  649. package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
  650. package/src/ux-sim/subagent-tools-device.ts +81 -0
  651. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
  652. package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
  653. package/src/version/packed-revision.json +1 -1
  654. package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
  655. package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
  658. package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
  660. package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
  662. package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
  663. package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/2987-30cf9c2eb7b8bdd5.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
  681. package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
  682. package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
  683. package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
  684. package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
  685. package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
  688. package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
  695. package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
  696. package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
  708. package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
  711. package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +0 -287
  717. package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
  722. package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
  725. package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
  738. package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
  740. package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
  742. package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
  744. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
  745. package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
  748. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
  750. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
  751. package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
  752. package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/term/page-df3445b207ceceab.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
  794. package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
  795. package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
  796. package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
  797. package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
  798. package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
  799. package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
  800. package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
  801. package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
  802. /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
@@ -1,23 +1,53 @@
1
- /** Fenced warm-Pod lease bridge with one-worker-per-host preparation. The Job remains authoritative. */
1
+ /** Fenced warm-Pod lease bridge. The Job remains authoritative. */
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
3
  import { isAbsolute, join } from 'node:path';
4
- import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
5
- import { tmpdir } from 'node:os';
4
+ import { constants, closeSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir, userInfo } from 'node:os';
6
+ import { dlopen, read } from 'bun:ffi';
7
+ import { createHash, randomUUID } from 'node:crypto';
6
8
  import { fileURLToPath } from 'node:url';
7
9
  import { debug } from '../../debug/log.js';
8
10
  import { getUserConfig } from '../../user-config.js';
9
- import { resolveWarmPoolProfile, resolveWarmPoolQuota, type WarmPoolTier } from '../../self-implement/pod-warm-pool-profile.js';
11
+ import { resolveWarmPoolProfile, resolveWarmPoolQuota, type WarmPoolProfile, type WarmPoolTier } from '../../self-implement/pod-warm-pool-profile.js';
10
12
  import { WarmPodTransport, probeWarmPodContext, warmWorkerPodManifest, type WarmPodSpec, type WarmPodTransportResult } from './pod-warm-worker-pod.js';
11
13
  import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
14
+ import { patchWarmLeaseMetadata, warmLeaseMetadata } from './pod-warm-provenance.js';
15
+ import type { LogStore } from '../../mss/logging/log-store.js';
12
16
 
13
- // Cold source install on node-b exceeded the old five-minute Ready wait (2026-10-20).
17
+ // Cold source install on node-b exceeded five minutes; recheck the pinned Pod UID in bounded intervals.
14
18
  const POD_READY_WAIT_SECONDS = 900;
19
+ const POD_READY_RECHECK_SECONDS = 300;
20
+ const POD_READY_RETRY_DELAY_MS = 1_000;
21
+
22
+ function throwIfPrepareAborted(signal?: AbortSignal): void {
23
+ if (!signal?.aborted) return;
24
+ const reason = signal.reason;
25
+ throw reason instanceof Error ? reason : new Error(reason === undefined ? 'warm prepare cancelled' : String(reason));
26
+ }
27
+
28
+ function waitForPrepareRetry(signal?: AbortSignal): Promise<void> {
29
+ throwIfPrepareAborted(signal);
30
+ return new Promise((resolve, reject) => {
31
+ const timer = setTimeout(() => {
32
+ signal?.removeEventListener('abort', abort);
33
+ resolve();
34
+ }, POD_READY_RETRY_DELAY_MS);
35
+ const abort = () => {
36
+ clearTimeout(timer);
37
+ signal?.removeEventListener('abort', abort);
38
+ try { throwIfPrepareAborted(signal); } catch (error) { reject(error); }
39
+ };
40
+ signal?.addEventListener('abort', abort, { once: true });
41
+ });
42
+ }
15
43
 
16
44
  export type WarmReplayInput = Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>;
17
45
  export interface WarmReplayResult { command: WarmWorkerCommand | null; transport: WarmPodTransportResult }
18
46
  export interface WarmWorkerCandidate { host: string; tier: WarmPoolTier; spec: WarmPodSpec }
19
47
  export interface WarmPodProviderOptions {
20
48
  candidates: readonly WarmWorkerCandidate[];
49
+ /** One pinned template per host/tier. Each expands to the measured profile quota. */
50
+ poolTemplates?: readonly WarmWorkerCandidate[];
21
51
  /** This address is resolved by the control-plane join/primary mechanism, never guessed from a Pod context. */
22
52
  controlUrl: string;
23
53
  memberToken: string;
@@ -30,6 +60,84 @@ export interface WarmPodProviderOptions {
30
60
  maxShadowMs?: number;
31
61
  now?: () => number;
32
62
  kubectl?: (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
63
+ /** Private directory for cross-process prepare leases; defaults to a per-user directory under tmpdir(). */
64
+ prepareLockDirectory?: string;
65
+ /** Cancels a prepare waiting for another process to finish the same host/worker slot. */
66
+ prepareAbortSignal?: AbortSignal;
67
+ /** Gate file this pool serves, used to bind cold prepare legs to their 1-file gate measurement key. */
68
+ pairingGateFile?: string;
69
+ }
70
+
71
+ export interface WarmPoolReconcileResult {
72
+ profile: string;
73
+ target: number;
74
+ ready: number;
75
+ failed: readonly string[];
76
+ }
77
+
78
+ /** One leg of a read-only warm/cold prepare-time measurement pair, bound to one 1-file gate key. */
79
+ export interface WarmPreparePairLeg {
80
+ file: string;
81
+ commit: string;
82
+ host: string;
83
+ /** True when an already-standing Pod was reused; false when this leg created the Pod. */
84
+ reuse: boolean;
85
+ prepareDurationMs: number;
86
+ totalDurationMs: number;
87
+ podUid: string;
88
+ success: boolean;
89
+ }
90
+ /** A read-only measurement row pairing the cold create and the warm reuse of the same 1-file gate. */
91
+ export interface WarmReuseTimePair {
92
+ gateKey: string;
93
+ file: string;
94
+ commit: string;
95
+ host: string;
96
+ cold: WarmPreparePairLeg | null;
97
+ warm: WarmPreparePairLeg | null;
98
+ }
99
+ /** The shared observation row each process emits (`pod.warm` / `warm-reuse-time-pair` in logs.db). */
100
+ export type WarmReuseTimeLegRow = WarmPreparePairLeg & { gateKey: string; leg: 'cold' | 'warm' };
101
+ const PAIR_CATEGORY = 'pod.warm';
102
+ const PAIR_EVENT = 'warm-reuse-time-pair';
103
+
104
+ function parseLegRow(value: unknown): WarmReuseTimeLegRow | null {
105
+ if (!value || typeof value !== 'object') return null;
106
+ const row = value as Record<string, unknown>;
107
+ const text = (key: string) => typeof row[key] === 'string';
108
+ const ms = (key: string) => typeof row[key] === 'number' && Number.isFinite(row[key] as number) && (row[key] as number) >= 0;
109
+ if (!(row.leg === 'cold' || row.leg === 'warm') || !text('gateKey') || !text('file') || !row.file
110
+ || !text('commit') || !text('host') || !text('podUid') || typeof row.reuse !== 'boolean'
111
+ || typeof row.success !== 'boolean' || !ms('prepareDurationMs') || !ms('totalDurationMs')) return null;
112
+ // The key must be the row's own file·commit·host; a mismatched key cannot join another gate.
113
+ if (row.gateKey !== `${row.file}/${row.commit}/${row.host}`) return null;
114
+ return row as unknown as WarmReuseTimeLegRow;
115
+ }
116
+
117
+ /** Pure read-only join: oldest→newest leg rows into one pair per 1-file gate key (newest leg wins). */
118
+ export function pairWarmReuseTimeLegs(rows: readonly unknown[]): WarmReuseTimePair[] {
119
+ const pairs = new Map<string, WarmReuseTimePair>();
120
+ for (const value of rows) {
121
+ const row = parseLegRow(value);
122
+ if (!row) continue;
123
+ const { gateKey, leg, ...measured } = row;
124
+ const pair = pairs.get(gateKey) ?? { gateKey, file: row.file, commit: row.commit, host: row.host, cold: null, warm: null };
125
+ pair[leg] = { file: measured.file, commit: measured.commit, host: measured.host, reuse: measured.reuse,
126
+ prepareDurationMs: measured.prepareDurationMs, totalDurationMs: measured.totalDurationMs,
127
+ podUid: measured.podUid, success: measured.success };
128
+ pairs.set(gateKey, pair);
129
+ }
130
+ return [...pairs.values()];
131
+ }
132
+
133
+ /** Read-only measurement rows from the logs.db both the cold CLI and the warm gate process write. */
134
+ export function readWarmReuseTimePairs(store: Pick<LogStore, 'queryAll'>, opts: { sinceMs?: number } = {}): WarmReuseTimePair[] {
135
+ const rows = store.queryAll({ exactCategories: [PAIR_CATEGORY], events: [PAIR_EVENT],
136
+ ...(opts.sinceMs !== undefined ? { sinceMs: opts.sinceMs } : {}) });
137
+ // queryAll is newest-first; replay oldest-first so the newest leg per key wins.
138
+ return pairWarmReuseTimeLegs(rows.slice().reverse().map(row => {
139
+ try { return row.data ? JSON.parse(row.data) : null; } catch { return null; }
140
+ }));
33
141
  }
34
142
 
35
143
  const fallback = (reason: 'no-candidate' | 'lease-unavailable'): WarmReplayResult =>
@@ -41,19 +149,120 @@ const token = (value: string | undefined): value is string => typeof value === '
41
149
  const workerId = (value: unknown): value is string => typeof value === 'string'
42
150
  && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,95}$/.test(value);
43
151
 
152
+ function checkedPrivateDirectory(path: string): void {
153
+ try { mkdirSync(path, { mode: 0o700 }); } catch (error) {
154
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
155
+ }
156
+ const info = lstatSync(path);
157
+ if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 0o077) !== 0
158
+ || (typeof process.getuid === 'function' && info.uid !== process.getuid()))
159
+ throw new Error('unsafe warm prepare lock directory');
160
+ }
161
+
162
+ const prepareLockLib = process.platform === 'linux' || process.platform === 'darwin'
163
+ ? dlopen(process.platform === 'linux' ? 'libc.so.6' : 'libSystem.B.dylib', {
164
+ flock: { args: ['int', 'int'], returns: 'int' },
165
+ [process.platform === 'linux' ? '__errno_location' : '__error']: { args: [], returns: 'ptr' },
166
+ }) : null;
167
+ const prepareFlock = prepareLockLib?.symbols.flock ?? null;
168
+ const prepareErrno = prepareLockLib?.symbols[process.platform === 'linux' ? '__errno_location' : '__error'];
169
+ const LOCK_EX = 2;
170
+ const LOCK_NB = 4;
171
+ const LOCK_UN = 8;
172
+ const PREPARE_LOCK_WAIT_MS = (POD_READY_WAIT_SECONDS * 1_000) + 30_000;
173
+
174
+ async function withPrepareLease<T>(candidate: WarmWorkerCandidate, directory: string | undefined,
175
+ work: () => Promise<T>, signal?: AbortSignal): Promise<T | null> {
176
+ const uid = typeof process.getuid === 'function' ? process.getuid() : userInfo().uid;
177
+ const root = directory ?? join(tmpdir(), `elanous-warm-prepare-${uid}`);
178
+ const identity = createHash('sha256').update(`${candidate.host}/${candidate.spec.workerId}`).digest('hex');
179
+ let fd: number | undefined;
180
+ let acquired = false;
181
+ try {
182
+ checkedPrivateDirectory(root);
183
+ const lock = join(root, `${identity}.flock`);
184
+ fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | (constants.O_NOFOLLOW ?? 0), 0o600);
185
+ const info = lstatSync(lock);
186
+ if (!info.isFile() || info.isSymbolicLink() || info.uid !== uid || (info.mode & 0o077) !== 0)
187
+ throw new Error('unsafe warm prepare lock file');
188
+ if (!prepareFlock) throw new Error('warm prepare OS locks are unavailable on this platform');
189
+ const lockDeadline = performance.now() + PREPARE_LOCK_WAIT_MS;
190
+ while (!acquired) {
191
+ if (signal?.aborted) throw new Error('warm prepare lock wait cancelled');
192
+ if (prepareFlock(fd, LOCK_EX | LOCK_NB) === 0) { acquired = true; break; }
193
+ const errno = prepareErrno ? read.i32(prepareErrno()) : -1;
194
+ // EWOULDBLOCK/EAGAIN: Linux 11, Darwin 35. EINTR is safe to retry.
195
+ if (errno !== 11 && errno !== 35 && errno !== 4)
196
+ throw new Error(`warm prepare flock failed (errno ${errno})`);
197
+ if (performance.now() >= lockDeadline) throw new Error('warm prepare lock wait timed out');
198
+ await new Promise<void>((resolve, reject) => {
199
+ const timer = setTimeout(done, Math.min(20, Math.max(1, lockDeadline - performance.now())));
200
+ const abort = () => { clearTimeout(timer); signal?.removeEventListener('abort', abort); reject(new Error('warm prepare lock wait cancelled')); };
201
+ function done() { signal?.removeEventListener('abort', abort); resolve(); }
202
+ signal?.addEventListener('abort', abort, { once: true });
203
+ });
204
+ }
205
+ return await work();
206
+ } catch (error) {
207
+ debug.log('pod.warm', 'prepare-lease-failed', { host: candidate.host,
208
+ workerId: candidate.spec.workerId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
209
+ throw error;
210
+ } finally {
211
+ if (acquired && fd !== undefined) prepareFlock?.(fd, LOCK_UN);
212
+ if (fd !== undefined) closeSync(fd);
213
+ }
214
+ }
215
+
44
216
  export class WarmPodProvider {
45
217
  private readonly fetcher: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
46
218
  private readonly now: () => number;
219
+ private readonly candidates: readonly WarmWorkerCandidate[];
220
+ private readonly profile: WarmPoolProfile;
221
+ /** This process's own legs, echoed on CLI stdout. Pairing never reads this — see readWarmReuseTimePairs. */
222
+ private readonly legs: WarmReuseTimeLegRow[] = [];
223
+ /** One terminal transition per fenced lease, including concurrent callers. Failed transitions remain retryable. */
224
+ private readonly terminalLeases = new Map<string, Promise<'sanitized' | 'quarantined'>>();
225
+ private readonly standingWorkers = new Map<string, Promise<boolean>>();
47
226
  constructor(private readonly options: WarmPodProviderOptions) {
48
227
  const url = new URL(options.controlUrl);
49
228
  if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
50
229
  || url.pathname !== '/' || !token(options.memberToken)) throw new Error('invalid warm control address');
51
- if (options.candidates.some(candidate => !workerId(candidate.spec.workerId)
52
- || !candidate.host.trim() || resolveWarmPoolQuota(resolveWarmPoolProfile(options.profileConfig ?? getUserConfig()), candidate.host, candidate.tier) < 1
230
+ const profile = resolveWarmPoolProfile(options.profileConfig ?? getUserConfig());
231
+ this.profile = profile;
232
+ const templates = options.poolTemplates ?? [];
233
+ if (new Set(templates.map(candidate => `${candidate.host}/${candidate.tier}`)).size !== templates.length)
234
+ throw new Error('duplicate warm pool template');
235
+ const expanded = templates.flatMap(candidate => {
236
+ const quota = resolveWarmPoolQuota(profile, candidate.host, candidate.tier);
237
+ if (!quota || !candidate.spec.nodeName) throw new Error('unmeasured warm pool template');
238
+ return Array.from({ length: quota }, (_, index): WarmWorkerCandidate => ({
239
+ host: candidate.host, tier: candidate.tier, spec: {
240
+ ...candidate.spec,
241
+ name: `warm-${candidate.host}-${candidate.tier}-${index + 1}`,
242
+ // The template worker ID is a generation token. Rotate it when a slot is quarantined.
243
+ workerId: `${candidate.spec.workerId}-${candidate.tier}-${index + 1}`,
244
+ },
245
+ }));
246
+ });
247
+ this.candidates = [...options.candidates, ...expanded];
248
+ if (this.candidates.some(candidate => !workerId(candidate.spec.workerId)
249
+ || !candidate.host.trim() || resolveWarmPoolQuota(profile, candidate.host, candidate.tier) < 1
53
250
  || candidate.spec.memoryLimit !== (candidate.tier === 'lite' ? '2Gi' : '16Gi')
54
251
  || !warmWorkerPodManifest(candidate.spec))) throw new Error('invalid warm candidate');
55
- if (new Set(options.candidates.map(candidate => candidate.spec.workerId)).size !== options.candidates.length)
252
+ if (new Set(this.candidates.map(candidate => candidate.spec.workerId)).size !== this.candidates.length)
56
253
  throw new Error('duplicate warm worker ID');
254
+ const names = new Set<string>();
255
+ const counts = new Map<string, number>();
256
+ for (const candidate of this.candidates) {
257
+ const identity = `${candidate.spec.kubectlContext}/${candidate.spec.namespace}/${candidate.spec.name}`;
258
+ if (names.has(identity)) throw new Error('duplicate warm Pod name');
259
+ names.add(identity);
260
+ const slot = `${candidate.host}/${candidate.tier}`;
261
+ const count = (counts.get(slot) ?? 0) + 1;
262
+ if (count > resolveWarmPoolQuota(profile, candidate.host, candidate.tier))
263
+ throw new Error('warm candidate quota exceeded');
264
+ counts.set(slot, count);
265
+ }
57
266
  this.fetcher = options.fetch ?? fetch;
58
267
  this.now = options.now ?? Date.now;
59
268
  }
@@ -117,14 +326,61 @@ export class WarmPodProvider {
117
326
  } finally { rmSync(directory, { recursive: true, force: true }); }
118
327
  }
119
328
 
120
- /** Prepare at most one standing worker per host. A foreign or replaced Pod is never deleted. */
329
+ /** Emit one measurement leg into the shared logs.db row the other process's leg joins. */
330
+ private recordPair(leg: 'cold' | 'warm', file: string, commit: string, host: string,
331
+ row: Omit<WarmPreparePairLeg, 'file' | 'commit' | 'host'>): void {
332
+ const legRow: WarmReuseTimeLegRow = { gateKey: `${file}/${commit}/${host}`, leg, file, commit, host, ...row };
333
+ this.legs.push(legRow);
334
+ debug.log(PAIR_CATEGORY, PAIR_EVENT, legRow, { level: 'info' });
335
+ }
336
+
337
+ /** The legs this process emitted (stdout echo only — pairing is readWarmReuseTimePairs over logs.db). */
338
+ warmReuseTimeLegs(): readonly WarmReuseTimeLegRow[] { return [...this.legs]; }
339
+
340
+ /** Prepare one named slot. A foreign or replaced Pod is never deleted. */
121
341
  async ensureStandingWorker(candidate: WarmWorkerCandidate): Promise<boolean> {
122
- const configured = this.options.candidates.find(row => row.host === candidate.host
342
+ const configured = this.candidates.find(row => row.host === candidate.host
343
+ && row.spec.workerId === candidate.spec.workerId);
344
+ if (!configured) return false;
345
+ const key = `${configured.host}/${configured.spec.workerId}`;
346
+ const inFlight = this.standingWorkers.get(key);
347
+ if (inFlight) return inFlight;
348
+ const preparation = withPrepareLease(configured, this.options.prepareLockDirectory,
349
+ () => this.prepareStandingWorker(configured, this.options.prepareAbortSignal), this.options.prepareAbortSignal)
350
+ .then(result => result === true);
351
+ this.standingWorkers.set(key, preparation);
352
+ try { return await preparation; }
353
+ finally { if (this.standingWorkers.get(key) === preparation) this.standingWorkers.delete(key); }
354
+ }
355
+
356
+ private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal): Promise<boolean> {
357
+ throwIfPrepareAborted(signal);
358
+ const startedAt = this.now();
359
+ const outcome: { preexisting: boolean; podUid: string | null } = { preexisting: false, podUid: null };
360
+ const ok = await this.coldPrepare(candidate, outcome, signal);
361
+ const totalDurationMs = this.now() - startedAt;
362
+ const gateFile = this.options.pairingGateFile;
363
+ // An unset gate file skips the measurement row entirely instead of recording an unpaired one.
364
+ if (gateFile) this.recordPair('cold', gateFile, candidate.spec.cutSha, candidate.host,
365
+ { reuse: outcome.preexisting, prepareDurationMs: outcome.preexisting ? 0 : totalDurationMs,
366
+ totalDurationMs, podUid: outcome.podUid ?? '', success: ok });
367
+ return ok;
368
+ }
369
+
370
+ private async coldPrepare(candidate: WarmWorkerCandidate,
371
+ outcome: { preexisting: boolean; podUid: string | null }, signal?: AbortSignal): Promise<boolean> {
372
+ const configured = this.candidates.find(row => row.host === candidate.host
123
373
  && row.spec.workerId === candidate.spec.workerId && row.spec.cutSha === candidate.spec.cutSha
124
374
  && keyEquals(row.spec.key, candidate.spec.key));
125
- // A single API name per host makes concurrent creates contend on Kubernetes object identity.
126
- if (!configured || !configured.spec.nodeName || configured.spec.name !== `warm-${configured.host}`
375
+ // Legacy slot warm-HOST and numbered profile slots can coexist during migration.
376
+ const prefix = configured ? `warm-${configured.host}` : '';
377
+ const suffix = configured?.spec.name.startsWith(`${prefix}-`)
378
+ ? configured.spec.name.slice(prefix.length + 1) : '';
379
+ if (!configured || !configured.spec.nodeName
380
+ || (configured.spec.name !== prefix && !/^[1-9][0-9]*$/.test(suffix)
381
+ && !/^(lite|standard)-[1-9][0-9]*$/.test(suffix))
127
382
  || candidate.spec.name !== configured.spec.name || candidate.spec.nodeName !== configured.spec.nodeName) return false;
383
+ throwIfPrepareAborted(signal);
128
384
  const { spec, host } = configured;
129
385
  const nodeName = spec.nodeName;
130
386
  if (!nodeName) return false;
@@ -205,7 +461,19 @@ export class WarmPodProvider {
205
461
  if (hostPods.length > 1) return false;
206
462
  let pod = hostPods[0];
207
463
  const preexisting = !!pod;
464
+ outcome.preexisting = preexisting;
208
465
  if (pod && !matches(pod)) return false;
466
+ // Old generations still occupy memory even when their ledger rows are quarantined.
467
+ // Count physical warm Pods on the pinned node before admitting a new one.
468
+ const physical = listed.items.filter((row: any) => row?.metadata?.labels?.['elanous.kind'] === 'warm-worker'
469
+ && row?.spec?.nodeName === nodeName && !row?.metadata?.deletionTimestamp
470
+ && row?.status?.phase !== 'Succeeded' && row?.status?.phase !== 'Failed'
471
+ && row?.spec?.containers?.[0]?.resources?.limits?.memory === spec.memoryLimit).length;
472
+ if (!preexisting && physical >= resolveWarmPoolQuota(this.profile, host, configured.tier)) {
473
+ debug.log('pod.warm', 'pool-slot-refused', { host, tier: configured.tier,
474
+ workerId: spec.workerId, reason: 'physical-quota', physical }, { level: 'warn' });
475
+ return false;
476
+ }
209
477
  const readLedger = async (): Promise<any[] | null> => {
210
478
  try {
211
479
  const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
@@ -217,15 +485,31 @@ export class WarmPodProvider {
217
485
  if (!pod) {
218
486
  // A worker ID already bound to another UID must not leave a newly created, unregistered Pod.
219
487
  const before = await readLedger();
488
+ throwIfPrepareAborted(signal);
220
489
  if (!before || before.length !== 0) return false;
221
490
  pod = parse(kubectl(['create', '-f', '-', '-o', 'json'], JSON.stringify(manifest)));
222
491
  if (!matches(pod)) return false;
223
492
  }
224
493
  const uid = pod.metadata.uid as string;
225
- if (kubectl(['wait', `pod/${spec.name}`, '--for=condition=Ready', `--timeout=${POD_READY_WAIT_SECONDS}s`]).status !== 0) return false;
226
- const ready = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
227
- if (!matches(ready) || ready.metadata.uid !== uid || ready.status?.phase !== 'Running'
228
- || !ready.status?.conditions?.some((row: any) => row.type === 'Ready' && row.status === 'True')) return false;
494
+ outcome.podUid = uid;
495
+ const ready = (value: any) => matches(value) && value.metadata.uid === uid
496
+ && value.status?.phase === 'Running'
497
+ && value.status?.conditions?.some((row: any) => row.type === 'Ready' && row.status === 'True');
498
+ const deadline = this.now() + POD_READY_WAIT_SECONDS * 1000;
499
+ let isReady = false;
500
+ while (this.now() < deadline) {
501
+ const remainingSeconds = Math.ceil((deadline - this.now()) / 1000);
502
+ const waitSeconds = Math.min(POD_READY_RECHECK_SECONDS, remainingSeconds);
503
+ throwIfPrepareAborted(signal);
504
+ kubectl(['wait', `pod/${spec.name}`, '--for=condition=Ready', `--timeout=${waitSeconds}s`]);
505
+ throwIfPrepareAborted(signal);
506
+ const observed = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
507
+ throwIfPrepareAborted(signal);
508
+ if (ready(observed)) { isReady = true; break; }
509
+ if (!matches(observed) || observed.metadata.uid !== uid) return false;
510
+ if (this.now() < deadline) await waitForPrepareRetry(signal);
511
+ }
512
+ if (!isReady) return false;
229
513
  // Re-read after create: a registration racing the preflight must fail before our POST.
230
514
  const rows = await readLedger();
231
515
  if (!rows) return false;
@@ -243,6 +527,7 @@ export class WarmPodProvider {
243
527
  // An unregistered Pod could have been created by anyone with namespace access.
244
528
  // Only this invocation's successful create response may become a new ledger row.
245
529
  if (preexisting) return false;
530
+ throwIfPrepareAborted(signal);
246
531
  let registered: { status: number; value: any };
247
532
  try {
248
533
  registered = await this.control('/v1/workers/pod', credential, {
@@ -257,36 +542,71 @@ export class WarmPodProvider {
257
542
  }
258
543
 
259
544
  async prepareWorker(workerId: string): Promise<boolean> {
260
- const candidate = this.options.candidates.find(row => row.spec.workerId === workerId);
545
+ const candidate = this.candidates.find(row => row.spec.workerId === workerId);
261
546
  if (!candidate) return false;
262
547
  return this.ensureStandingWorker(candidate);
263
548
  }
264
549
 
550
+ /** One bounded reconciliation pass. Failed slots stay unavailable until a fresh worker ID is configured. */
551
+ async reconcilePool(): Promise<WarmPoolReconcileResult> {
552
+ const failed: string[] = [];
553
+ let ready = 0;
554
+ for (const candidate of this.candidates) {
555
+ let prepared = false;
556
+ try { prepared = await this.ensureStandingWorker(candidate); }
557
+ catch { /* Uncertain state is never counted as ready. */ }
558
+ if (prepared) ready++;
559
+ else failed.push(candidate.spec.workerId);
560
+ debug.log('pod.warm', 'pool-slot', { profile: this.profile,
561
+ host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
562
+ pod: candidate.spec.name, ready: prepared }, { level: prepared ? 'info' : 'warn' });
563
+ }
564
+ const result = { profile: this.profile,
565
+ target: this.candidates.length, ready, failed };
566
+ debug.log('pod.warm', 'pool-reconcile', result, { level: failed.length ? 'warn' : 'info' });
567
+ return result;
568
+ }
569
+
265
570
  /** Acquire a matching idle worker before exec. Every uncertain result is fenced out of reuse. */
266
571
  async request(input: WarmReplayInput): Promise<WarmReplayResult> {
572
+ const requestedAt = this.now();
267
573
  if (input.protocol !== 1 || input.envProfile !== 'gate-v1' || !Array.isArray(input.files)
268
574
  || !input.files.length || !Number.isFinite(input.deadlineAt) || input.deadlineAt <= this.now()) return fallback('no-candidate');
269
- const candidate = this.options.candidates.find(row => row.spec.cutSha === input.cutSha);
270
- if (!candidate) return fallback('no-candidate');
575
+ const matches = this.candidates.filter(row => row.spec.cutSha === input.cutSha);
576
+ if (!matches.length) return fallback('no-candidate');
577
+ let lastFallback: WarmReplayResult = fallback('lease-unavailable');
578
+ // The control plane chooses an idle worker within a host/key. Probe each distinct
579
+ // host/key once so a busy first host cannot strand an idle second pool.
580
+ const tried = new Set<string>();
581
+ for (const candidate of matches) {
271
582
  const { spec, host } = candidate;
583
+ const poolKey = `${host}/${spec.key.context}/${spec.key.imageDigest}/${spec.key.lockHash}/${spec.key.architecture}`;
584
+ if (tried.has(poolKey)) continue;
585
+ tried.add(poolKey);
272
586
  const contextFailure = probeWarmPodContext(spec.kubectlContext, this.options.kubectl);
273
- if (contextFailure) return { command: null, transport: contextFailure };
587
+ if (contextFailure) { lastFallback = { command: null, transport: contextFailure }; continue; }
274
588
  let acquired: { status: number; value: any };
275
589
  try {
276
590
  acquired = await this.control('/v1/leases/pod-worker', this.options.memberToken, {
277
591
  machine: host, key: spec.key, runId: input.runId, attemptId: input.attemptId,
278
592
  ttlMs: this.options.leaseTtlMs ?? 30 * 60_000,
279
593
  });
280
- } catch { return fallback('lease-unavailable'); }
594
+ } catch { lastFallback = fallback('lease-unavailable'); continue; }
595
+ if (acquired.status === 409 || acquired.status === 503) {
596
+ lastFallback = fallback('lease-unavailable');
597
+ continue;
598
+ }
281
599
  if (acquired.status !== 200) return fallback('lease-unavailable');
282
600
  const row = acquired.value;
283
601
  const leasedId = row?.attrs?.leaseId;
284
602
  const generation = row?.attrs?.generation;
603
+ const podUid = row?.attrs?.podUid;
285
604
  if (!workerId(row?.name) || typeof leasedId !== 'string' || !leasedId
605
+ || typeof podUid !== 'string' || !podUid
286
606
  || !Number.isSafeInteger(generation) || generation < 1) return fallback('lease-unavailable');
287
607
  // Acquisition selects by host/key, not by worker ID. Resolve the returned worker
288
608
  // before choosing its transport; an unknown worker must not be executed or fenced.
289
- const leasedCandidate = this.options.candidates.find(entry => entry.host === host
609
+ const leasedCandidate = this.candidates.find(entry => entry.host === host
290
610
  && entry.spec.workerId === row.name && entry.spec.cutSha === input.cutSha
291
611
  && keyEquals(entry.spec.key, spec.key));
292
612
  if (!leasedCandidate) {
@@ -295,16 +615,39 @@ export class WarmPodProvider {
295
615
  return fallback('lease-unavailable');
296
616
  }
297
617
  if (!row.attrs?.key || !keyEquals(row.attrs.key, leasedCandidate.spec.key)) {
298
- const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation,
618
+ const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation, podUid,
619
+ requestedAt: new Date(this.now()).toISOString(),
299
620
  workerId: row.name, key: leasedCandidate.spec.key };
300
- await this.quarantine(command, 'candidate-identity-mismatch');
621
+ await this.quarantine(command, 'candidate-identity-mismatch', false);
301
622
  return fallback('lease-unavailable');
302
623
  }
303
- const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation,
624
+ const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation, podUid,
625
+ podName: leasedCandidate.spec.name, podContext: leasedCandidate.spec.kubectlContext, image: leasedCandidate.spec.image,
626
+ requestedAt: new Date(this.now()).toISOString(),
304
627
  workerId: row.name, key: leasedCandidate.spec.key };
628
+ const admitted = patchWarmLeaseMetadata(leasedCandidate.spec, command, 'leased', this.options.kubectl);
629
+ if (!admitted.ok) {
630
+ debug.log('pod.provenance', 'lease-patch-refused', { pod: leasedCandidate.spec.name,
631
+ podUid, leaseId: leasedId, reason: admitted.reason }, { level: 'warn' });
632
+ await this.quarantine(command, 'provenance-unavailable', false);
633
+ return fallback('lease-unavailable');
634
+ }
635
+ debug.log('pod.provenance', 'lease-start', { pod: leasedCandidate.spec.name, podUid,
636
+ leaseId: leasedId, runId: command.runId, attemptId: command.attemptId,
637
+ requestedAt: command.requestedAt, metadata: warmLeaseMetadata(command).annotations });
305
638
  let transport: WarmPodTransportResult;
306
639
  try { transport = await this.runTransport(leasedCandidate.spec, command); }
307
640
  catch { transport = { status: 'fallback', reason: 'exec-failed', reply: null }; }
641
+ // Warm leg: the lease reused an already-standing Pod, so no prepare interval elapsed.
642
+ // Only a 1-file gate has a pairing key; a multi-file gate is skipped (with its reason), never keyed by files[0].
643
+ const gateFile = command.files.length === 1 ? command.files[0] : undefined;
644
+ if (command.files.length !== 1) debug.log(PAIR_CATEGORY, `${PAIR_EVENT}-skipped`, { reason: 'not-single-file-gate',
645
+ fileCount: command.files.length, commit: command.cutSha, host }, { level: 'info' });
646
+ if (gateFile) this.recordPair('warm', gateFile, command.cutSha, host, {
647
+ reuse: true, prepareDurationMs: 0,
648
+ totalDurationMs: this.now() - requestedAt, podUid,
649
+ success: transport.status === 'observed' && transport.reason === 'complete',
650
+ });
308
651
  if (transport.status !== 'observed' || transport.reason !== 'complete') {
309
652
  await this.quarantine(command, transport.reason);
310
653
  return { command, transport };
@@ -312,6 +655,8 @@ export class WarmPodProvider {
312
655
  // Keep the lease fenced until the gate has compared these bytes with its Job baseline.
313
656
  // A missing finalization leaves the worker leased and therefore unavailable.
314
657
  return { command, transport };
658
+ }
659
+ return lastFallback;
315
660
  }
316
661
 
317
662
  private async transition(command: WarmWorkerCommand, action: 'release' | 'sanitize' | 'quarantine',
@@ -325,21 +670,72 @@ export class WarmPodProvider {
325
670
  }
326
671
 
327
672
  async finalize(command: WarmWorkerCommand): Promise<void> {
328
- const candidate = this.options.candidates.find(row => row.spec.workerId === command.workerId);
673
+ const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
674
+ const prior = this.terminalLeases.get(key);
675
+ if (prior) {
676
+ const outcome = await prior;
677
+ debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
678
+ leaseId: command.leaseId, generation: command.generation, requested: 'finalize', outcome });
679
+ if (outcome === 'quarantined') throw new Error('warm lease already quarantined');
680
+ return;
681
+ }
682
+ const terminal = this.finalizeOnce(command);
683
+ this.terminalLeases.set(key, terminal);
684
+ let outcome: 'sanitized' | 'quarantined';
685
+ try { outcome = await terminal; }
686
+ catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
687
+ if (outcome === 'quarantined') throw new Error('warm sanitize unavailable');
688
+ }
689
+
690
+ private async finalizeOnce(command: WarmWorkerCommand): Promise<'sanitized' | 'quarantined'> {
691
+ const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
329
692
  if (!candidate) throw new Error('warm finalize candidate missing');
330
693
  const released = await this.transition(command, 'release');
331
694
  const machineToken = this.options.machineTokens?.[candidate.host];
332
695
  // Sanitizing is the producer's machine-scoped acknowledgement, after transport's cleanup and ACK.
333
- if (!released || !token(machineToken) || !await this.transition(command, 'sanitize', machineToken)) {
334
- await this.quarantine(command, 'sanitize-unavailable');
335
- throw new Error('warm sanitize unavailable');
696
+ const cleared = released ? patchWarmLeaseMetadata(candidate.spec, command, 'idle', this.options.kubectl)
697
+ : { ok: false, reason: 'release-failed' };
698
+ if (!cleared.ok) debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
699
+ podUid: command.podUid, leaseId: command.leaseId, reason: cleared.reason }, { level: 'warn' });
700
+ if (!cleared.ok || !token(machineToken) || !await this.transition(command, 'sanitize', machineToken)) {
701
+ await this.quarantineOnce(command, 'sanitize-unavailable', true);
702
+ return 'quarantined';
703
+ }
704
+ debug.log('pod.provenance', 'lease-end', { pod: candidate.spec.name, podUid: command.podUid,
705
+ leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId,
706
+ releasedAt: new Date(this.now()).toISOString(), result: 'sanitized' });
707
+ return 'sanitized';
708
+ }
709
+
710
+ /** `leaseStarted=false` marks a refusal before `lease-start`, so start/end history stays paired. */
711
+ async quarantine(command: WarmWorkerCommand, reason: string, leaseStarted = true): Promise<void> {
712
+ const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
713
+ const prior = this.terminalLeases.get(key);
714
+ if (prior) {
715
+ const outcome = await prior;
716
+ debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
717
+ leaseId: command.leaseId, generation: command.generation, requested: 'quarantine', outcome });
718
+ return;
336
719
  }
720
+ const terminal = this.quarantineOnce(command, reason, leaseStarted).then(() => 'quarantined' as const);
721
+ this.terminalLeases.set(key, terminal);
722
+ try { await terminal; }
723
+ catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
337
724
  }
338
725
 
339
- async quarantine(command: WarmWorkerCommand, reason: string): Promise<void> {
726
+ private async quarantineOnce(command: WarmWorkerCommand, reason: string, leaseStarted: boolean): Promise<void> {
340
727
  if (!await this.transition(command, 'quarantine', this.options.memberToken, reason.slice(0, 120) || 'mismatch')) {
341
728
  throw new Error('warm quarantine unavailable');
342
729
  }
730
+ const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
731
+ if (candidate) {
732
+ const cleared = patchWarmLeaseMetadata(candidate.spec, command, 'quarantined', this.options.kubectl);
733
+ if (!cleared.ok && reason !== 'provenance-unavailable') debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
734
+ podUid: command.podUid, leaseId: command.leaseId, reason: cleared.reason }, { level: 'warn' });
735
+ }
736
+ debug.log('pod.provenance', leaseStarted ? 'lease-end' : 'lease-refused', { podUid: command.podUid,
737
+ leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId, leaseStarted,
738
+ releasedAt: new Date(this.now()).toISOString(), result: 'quarantined', reason });
343
739
  }
344
740
 
345
741
 
@@ -349,11 +745,17 @@ let configuredProvider: WarmPodProvider | null = null;
349
745
  /** The trusted producer configures this once; an absent provider fails closed without creating a Pod. */
350
746
  export function configureWarmPodProvider(provider: WarmPodProvider | null): void { configuredProvider = provider; }
351
747
  /** Optional private producer config; a missing/unsafe file leaves the Job path untouched. */
352
- export function loadWarmPodProvider(path: string): WarmPodProvider {
748
+ export function loadWarmPodProvider(path: string, pairingGateFile?: string): WarmPodProvider {
353
749
  if (!isAbsolute(path) || (statSync(path).mode & 0o077) !== 0) throw new Error('warm provider config must be private');
354
750
  const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
355
751
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid warm provider config');
356
- return new WarmPodProvider(parsed as WarmPodProviderOptions);
752
+ const options = parsed as WarmPodProviderOptions;
753
+ // The production entry points (--prepare/--reconcile/--serve) bind the 1-file gate key here.
754
+ if (pairingGateFile !== undefined) {
755
+ if (!pairingGateFile.trim()) throw new Error('warm provider pairing gate file must not be empty');
756
+ options.pairingGateFile = pairingGateFile;
757
+ }
758
+ return new WarmPodProvider(options);
357
759
  }
358
760
  function currentProvider(): WarmPodProvider | null {
359
761
  if (configuredProvider) return configuredProvider;
@@ -364,7 +766,13 @@ function currentProvider(): WarmPodProvider | null {
364
766
  return configuredProvider;
365
767
  }
366
768
  export async function requestWarmPodReplay(input: WarmReplayInput): Promise<WarmReplayResult> {
367
- return currentProvider() ? currentProvider()!.request(input) : fallback('no-candidate');
769
+ const provider = currentProvider();
770
+ const result = provider ? await provider.request(input) : fallback('no-candidate');
771
+ debug.log('pod.warm', 'request-selection', { runId: input.runId, attemptId: input.attemptId,
772
+ cutSha: input.cutSha, selected: result.command !== null,
773
+ workerId: result.command?.workerId ?? null, status: result.transport.status,
774
+ reason: result.transport.reason });
775
+ return result;
368
776
  }
369
777
  export async function quarantineWarmPodReplay(command: WarmWorkerCommand, reason: string): Promise<void> {
370
778
  const provider = currentProvider();
@@ -377,13 +785,86 @@ export async function finalizeWarmPodReplay(command: WarmWorkerCommand): Promise
377
785
  await provider.finalize(command);
378
786
  }
379
787
 
788
+
789
+ /** CLI 인자 — `--prepare <workerId> [gateFile]` · `--reconcile [gateFile]` · `--serve [gateFile]` · `--pairs`.
790
+ * gateFile 은 --prepare 에서만 셋째 칸, 나머지는 둘째 칸이다. `--pairs` 는 logs.db 읽기 전용(인자 없음). */
791
+ export function parseWarmPodProviderArgs(argv: readonly string[]): { action: '--prepare' | '--reconcile' | '--serve' | '--pairs'; workerId?: string; gateFile?: string } {
792
+ const usage = () => new Error('usage: --prepare <workerId> [gateFile] | --reconcile [gateFile] | --serve [gateFile]'
793
+ + ' with private provider config | --pairs');
794
+ if (argv[0] === '--pairs') {
795
+ if (argv.length !== 1) throw usage();
796
+ return { action: '--pairs' };
797
+ }
798
+ const [action, ...rest] = argv;
799
+ if (action === '--prepare') {
800
+ const [workerId, gateFile, extra] = rest;
801
+ if (!workerId || extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
802
+ return { action, workerId, ...(gateFile !== undefined ? { gateFile } : {}) };
803
+ }
804
+ if (action === '--reconcile' || action === '--serve') {
805
+ const [gateFile, extra] = rest;
806
+ if (extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
807
+ return { action, ...(gateFile !== undefined ? { gateFile } : {}) };
808
+ }
809
+ throw usage();
810
+ }
811
+
380
812
  if (import.meta.main) {
381
- const [action, workerId] = process.argv.slice(2);
813
+ const { action, workerId, gateFile } = parseWarmPodProviderArgs(process.argv.slice(2));
382
814
  const file = process.env.ELANOUS_WARM_POD_PROVIDER_CONFIG;
383
- if (action !== '--prepare' || !workerId) throw new Error('usage: --prepare <workerId> with private provider config');
384
- let prepared = false;
385
- try { if (file) prepared = await loadWarmPodProvider(file).prepareWorker(workerId); }
386
- catch { /* An invalid private config is a failed preparation, never a Pod mutation. */ }
387
- process.stdout.write(JSON.stringify({ workerId, prepared }) + '\n');
388
- if (!prepared) process.exitCode = 2;
815
+ // The 1-file gate key this pool serves, bound into every cold prepare leg of this process.
816
+ const load = () => (file ? loadWarmPodProvider(file, gateFile) : null);
817
+ if (action === '--pairs') {
818
+ // Read-only: join the cold CLI legs and the warm gate legs both processes wrote to logs.db.
819
+ try {
820
+ const storeMod = await import('../../mss/logging/log-store.js');
821
+ const store = storeMod.LogStore.openReadOnly(storeMod.logsDbPath());
822
+ try { process.stdout.write(JSON.stringify({ warmReuseTimePairs: readWarmReuseTimePairs(store) }) + '\n'); }
823
+ finally { store.close(); }
824
+ } catch {
825
+ process.stdout.write(JSON.stringify({ warmReuseTimePairs: null, reason: 'logs-unavailable' }) + '\n');
826
+ process.exitCode = 2;
827
+ }
828
+ }
829
+ // The cold legs must reach the same logs.db the warm gate process (gate-node) writes; fail-open.
830
+ if (action !== '--pairs') try { await (await import('../../domains/standalone-log-sink.js')).registerStandaloneLogSink('pod-warm'); }
831
+ catch { /* the file trail stays the source of truth */ }
832
+ // This process's own legs, echoed for the caller; the pair itself is read with --pairs.
833
+ const emitPairs = (provider: WarmPodProvider) => {
834
+ const legs = provider.warmReuseTimeLegs();
835
+ if (legs.length) process.stdout.write(JSON.stringify({ warmReuseTimeLegs: legs }) + '\n');
836
+ };
837
+ if (action === '--serve') {
838
+ // Reloading the private config lets the owner rotate a quarantined slot to a new worker ID.
839
+ // The old ID remains fenced in the ledger; this loop never deletes a Pod or rewrites the ledger.
840
+ while (true) {
841
+ try {
842
+ const provider = load();
843
+ const result = provider ? await provider.reconcilePool() : null;
844
+ process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
845
+ if (provider) emitPairs(provider);
846
+ } catch {
847
+ debug.log('pod.warm', 'pool-reconcile-unavailable', { reason: 'config-unavailable' }, { level: 'warn' });
848
+ process.stdout.write(JSON.stringify({ target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
849
+ }
850
+ await Bun.sleep(60_000);
851
+ }
852
+ }
853
+ if (action === '--reconcile') {
854
+ let result: WarmPoolReconcileResult | null = null;
855
+ let provider: WarmPodProvider | null = null;
856
+ try { provider = load(); result = provider ? await provider.reconcilePool() : null; }
857
+ catch { /* An invalid private config is a failed pass, never a Pod mutation. */ }
858
+ process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
859
+ if (provider) emitPairs(provider);
860
+ if (!result || result.ready !== result.target) process.exitCode = 2;
861
+ } else if (action === '--prepare') {
862
+ let prepared = false;
863
+ let provider: WarmPodProvider | null = null;
864
+ try { provider = load(); prepared = provider ? await provider.prepareWorker(workerId!) : false; }
865
+ catch { /* An invalid private config is a failed preparation, never a Pod mutation. */ }
866
+ process.stdout.write(JSON.stringify({ workerId, prepared }) + '\n');
867
+ if (provider) emitPairs(provider);
868
+ if (!prepared) process.exitCode = 2;
869
+ }
389
870
  }