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.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → 053a429e.9a63436527cc751c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +287 -0
- package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +64 -0
- package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +36 -0
- package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +149 -0
- package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 2683.c9773a0c589b031b.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 275-02ea4c2a33980422.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{ffdc3a04.b9c23c1b28dd4a96.js → 2812ca6a.3b9f4a7a08885744.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 2986.45d7652e4f13557e.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3031-85328a92fcce4da6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8817.61bb7ffbba502ecb.js → 3136.30aaaa5fa03662a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3148-98efd4869d660a87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3498.cf2a15a8a9e5b926.js → 3155.2587a55196ecb394.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3103.84470c8445962f04.js → 3324.a09703033abcbd2f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +8 -0
- package/apps/pwa/out/_next/static/chunks/3410.7f9af5ab77cbe65f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/{3819.163d4777f0a597f6.js → 3538.a445e50bb359d068.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +286 -0
- package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 3727.7949ccaacaa71242.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → 3a3d9966.cc4d4585a2708e7c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4116.a84a92a2492c080e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +104 -0
- package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 4286.b70c4520306817e8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4304.e9b2512834a9e23e.js +23 -0
- package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 4615.8525eb0add35eba6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +148 -0
- package/apps/pwa/out/_next/static/chunks/{3411-f87314c6c66ebc73.js → 5085-bce4904931767ca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 528.38ce20ef8aa251ff.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +122 -0
- package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 552.b7d061ad91eeac51.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3837.722e053fb9f321ad.js → 5711.957b095705c12560.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 575.d929793a53f27c4b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7562.948255cbc3e79d33.js → 599.c39c571b6733f2a2.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +79 -0
- package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6566.926a1d1b7c911442.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +1 -0
- package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +27 -0
- package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5446.edf141cff9ef647d.js → 6925.562d532110879886.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 6943.c6b24124853a47b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 7481.39f7a4654907bf6e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 771.c3cdb6c1f7f47c02.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/7784.1f0ac4a16d190cf4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 7891.03f910124a737985.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8037.e254fd797aa224d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +1 -0
- package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → 873d6c12.09bd499f57171543.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 8904.7d51af2dc033965c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +90 -0
- package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 9250-072df22eaadaa6b5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +77 -0
- package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 9514.131c52cc75fe6439.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9f31e478.fefcbcd3c388ebc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/{page-4fb31eb476dcbad3.js → page-083747f1d4518f61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e7abc34a48a6f71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-c15958835534404b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → ceba0382.650712b56b85b582.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/da5ecc5c-b005ad8cac737d57.js +1 -0
- package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +1 -0
- package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.mjs +3 -2
- package/catalog/providers/opengateway.yaml +25 -0
- package/docs/goal-context/01-goal-authoring-checks.md +8 -0
- package/docs/goal-context/03-verification-menu.md +24 -0
- package/docs/goal-context/04-mock-scenarios.md +100 -0
- package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
- package/docs/goal-context/README.md +7 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/default-loop.yaml +63 -4
- package/graphs/document-loop.yaml +51 -3
- package/graphs/implement-loop.yaml +77 -4
- package/graphs/journey/ta-journey.yaml +11 -1
- package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
- package/graphs/journey/units/author-dialogue/observe.ts +96 -0
- package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
- package/graphs/operate-loop.yaml +51 -3
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +17 -4
- package/graphs/research-loop.yaml +137 -3
- package/graphs/test-diet/test-diet.yaml +3 -0
- package/graphs/wizard/recipes.yaml +44 -0
- package/graphs/wizard/wizard-loop.yaml +44 -0
- package/graphs/work/research-report.yaml +2 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +139 -5
- package/scripts/coord-role-pack.ts +19 -6
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/hq/standby-snapshot.ts +17 -12
- package/scripts/release-loop/docs-land-node.ts +21 -5
- package/scripts/release-loop/docs-node.ts +204 -8
- package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
- package/scripts/release-loop/gate-node.ts +427 -72
- package/scripts/release-loop/gate-partial.ts +65 -11
- package/scripts/release-loop/gate-radius-live.ts +177 -0
- package/scripts/release-loop/gate-radius-shadow.ts +1 -1
- package/scripts/release-loop/node-verdict.ts +14 -1
- package/scripts/release-loop/npm-publish-node.ts +66 -12
- package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
- package/scripts/release-loop/pwa-remote.ts +166 -0
- package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +78 -15
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/test-diet/lib.ts +6 -1
- package/scripts/test-diet/node.ts +194 -13
- package/scripts/ux-sim.ts +92 -0
- package/skills/README.md +1 -0
- package/src/acp/core-turn-bridge.ts +48 -6
- package/src/agent/delegate-hint.ts +16 -0
- package/src/agent/runner.ts +82 -2
- package/src/agent/self-ambient-cache.ts +8 -0
- package/src/agent/task-notification.ts +56 -10
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/acp-reviewer.ts +39 -4
- package/src/agent-substrate/pr-reviewer.ts +72 -3
- package/src/agent-substrate/review-intent.ts +29 -2
- package/src/agent-substrate/review-observation.ts +3 -0
- package/src/agent-substrate/self-review-cli.ts +229 -28
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +240 -41
- package/src/boot/daemon-tools/self-implement.ts +1 -0
- package/src/budget/run-usage-rollup.ts +1 -0
- package/src/budget/usage-outcomes.ts +8 -4
- package/src/capture/tui-self-report.ts +102 -0
- package/src/chat/goal-continue-verdict.ts +41 -0
- package/src/chat/index.ts +30 -6
- package/src/chat/internal-turn-origin.ts +1 -0
- package/src/chat/paste-fold.ts +86 -0
- package/src/chat/pickers/modal-runtime.ts +10 -27
- package/src/chat/tool-render/block.ts +32 -0
- package/src/chat/tool-render/dispatch.ts +2 -20
- package/src/chat/tool-render/result-text.ts +98 -0
- package/src/chat/turn-typeahead.ts +70 -7
- package/src/cli/agent-cli.ts +13 -2
- package/src/cli/config-audit.ts +119 -0
- package/src/cli/doctor-cli.ts +26 -1
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/herdr-cli.ts +34 -0
- package/src/cli/light-entry-route.ts +50 -7
- package/src/cli/log-event-names.ts +2 -0
- package/src/cli/make-cli.ts +247 -11
- package/src/cli/msg-cli.ts +117 -3
- package/src/cli/pod-cli.ts +77 -3
- package/src/cli/pr-cli.ts +34 -18
- package/src/cli/pr-land-base-read.ts +11 -2
- package/src/cli/pr-land-heavy-remote.ts +6 -3
- package/src/cli/pr-land-overlap-evidence.ts +23 -4
- package/src/cli/pr-land-pod-shadow-job.ts +3 -0
- package/src/cli/pr-land-recovery.ts +117 -1
- package/src/cli/pty-takeover-cli.ts +8 -7
- package/src/cli/release-checklist-read-cli.ts +79 -0
- package/src/cli/release-cli.ts +92 -106
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/remote-resolve.ts +1 -1
- package/src/cli/root-tui-entry.ts +48 -0
- package/src/cli/sim-cli.ts +22 -0
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +1065 -338
- package/src/cli/tasks-read-cli.ts +197 -0
- package/src/cli/usage-cli.ts +13 -11
- package/src/clipboard/history.ts +179 -13
- package/src/config.ts +8 -1
- package/src/core-turn/run-core-turn.ts +4 -2
- package/src/core-turn/run-goal-loop.ts +73 -16
- package/src/core-turn/types.ts +6 -1
- package/src/dashboard/chat/acp-chat.ts +8 -0
- package/src/dashboard/context/keys.ts +8 -31
- package/src/dashboard/index.ts +280 -59
- package/src/dashboard/input/agent-wake.ts +7 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
- package/src/dashboard/input/chat-main-slash-command.ts +4 -2
- package/src/dashboard/input/goal-continue.ts +210 -0
- package/src/dashboard/input/goal-interim.ts +2 -0
- package/src/dashboard/input/internal-turn.ts +2 -8
- package/src/dashboard/input/seat-autowake.ts +9 -7
- package/src/dashboard/input/turn-autoheal.ts +52 -0
- package/src/dashboard/render/theme-resolver.ts +33 -4
- package/src/dashboard/seat-boot-preamble.ts +132 -5
- package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
- package/src/dashboard/slash-runtime/model-choices.ts +15 -0
- package/src/dashboard/stale-install-check.ts +3 -99
- package/src/debug/batch-queue.ts +119 -0
- package/src/debug/tui-log-tag.ts +67 -0
- package/src/display/agent-surface.ts +7 -0
- package/src/domains/schedule-registry.ts +16 -2
- package/src/domains/standalone-log-sink.ts +15 -2
- package/src/esc-abort-gate.ts +69 -0
- package/src/git-fs/idle-refresh.ts +76 -0
- package/src/git-fs/index.ts +18 -7
- package/src/git-fs/worktree.ts +137 -5
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-cli.ts +18 -0
- package/src/graph-runner/runner.ts +173 -21
- package/src/graph-wizard/generate.ts +108 -4
- package/src/graph-wizard/steps.ts +15 -0
- package/src/grok/credential.ts +10 -3
- package/src/grounding/proactive-ground.ts +86 -0
- package/src/harness/control-inbox.ts +49 -1
- package/src/harness/harness-cli-command.ts +163 -35
- package/src/harness/harness-incidents.ts +85 -1
- package/src/harness/launch-utterance-ledger.ts +208 -0
- package/src/harness/pod-control-send.ts +33 -5
- package/src/harness/self-send-remote.ts +69 -0
- package/src/harness/self-send-routing-guard.ts +6 -0
- package/src/harness/self-send-target.ts +4 -3
- package/src/impl-discipline/detector.ts +16 -4
- package/src/impl-discipline/system-prompt.ts +11 -0
- package/src/impl-discipline/types.ts +2 -2
- package/src/index.ts +822 -154
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/intake-plane/already-built-hint.ts +4 -2
- package/src/intake-plane/requirement-funnel.ts +29 -1
- package/src/light-cli.ts +32 -11
- package/src/llm/opengateway-models.ts +11 -0
- package/src/llm/provider-credentials.ts +14 -0
- package/src/llm/provider-detect.ts +8 -1
- package/src/llm/runtime-model-family.ts +2 -1
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +247 -59
- package/src/loops/msg-wake.ts +35 -39
- package/src/loops/orchestrator/inbox.ts +59 -11
- package/src/loops/orchestrator/knowhow-pack.ts +364 -0
- package/src/loops/orchestrator/tick.ts +5 -1
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/model-tier/llm-tier-map.ts +15 -1
- package/src/models/prompts.ts +17 -2
- package/src/msg/msg-post-office.ts +116 -0
- package/src/mss/logging/log-store.ts +79 -22
- package/src/native-tool-catalog.ts +15 -1
- package/src/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +20 -2
- package/src/nexus/api/loop-resources.ts +13 -1
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/oauth/codex.ts +7 -0
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/overview-collect.ts +30 -9
- package/src/ops/overview.ts +22 -9
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- package/src/pr-tool/census.ts +261 -27
- package/src/prompt-library/session-guidance.ts +14 -0
- package/src/provider-summary.ts +8 -0
- package/src/pty-shell/herdr-agent-report.ts +118 -9
- package/src/pty-shell/herdr-prompt.ts +266 -0
- package/src/pty-shell/pty-control-ipc.ts +151 -8
- package/src/pty-shell/pty-manifest.ts +7 -0
- package/src/pty-shell/pty-special-keys.ts +42 -2
- package/src/pty-shell/stale-install-check.ts +99 -0
- package/src/pty-shell/tui-run-state.ts +14 -2
- package/src/release-loop/auto-start.ts +20 -2
- package/src/release-loop/checklist.ts +100 -4
- package/src/release-loop/landed-but-yellow.ts +129 -5
- package/src/release-loop/light-rc.ts +1 -0
- package/src/release-loop/rehearsal.ts +37 -21
- package/src/repl/index.ts +192 -17
- package/src/repl/ops-msg-bridge.ts +32 -0
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/self-dev/ask-launch-flow.ts +44 -5
- package/src/self-dev/cli-help-text.ts +17 -0
- package/src/self-dev/decompose-proposal.ts +172 -20
- package/src/self-dev/decompose.ts +61 -0
- package/src/self-dev/dev-cli.ts +48 -12
- package/src/self-dev/dev-pipeline.ts +8 -2
- package/src/self-dev/draft-residue.ts +37 -1
- package/src/self-dev/draft-sweep.ts +137 -11
- package/src/self-dev/draft-triage-rules.ts +138 -0
- package/src/self-dev/fleet-reduce.ts +3 -2
- package/src/self-dev/glm-credential-guard.ts +77 -0
- package/src/self-dev/graph-front-nodes.ts +112 -0
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/graph-journey-route.ts +12 -2
- package/src/self-dev/harness-run-cli.ts +3 -15
- package/src/self-dev/orchestrate-cli-text.ts +10 -0
- package/src/self-dev/orchestrate-cli.ts +68 -3
- package/src/self-dev/orchestrate.ts +116 -17
- package/src/self-dev/review-provider-fallback.ts +35 -1
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-dev/self-orchestrate-runtime.ts +1 -11
- package/src/self-dev/shard-aggregate.ts +42 -5
- package/src/self-dev/shard-run.ts +8 -1
- package/src/self-health/autoheal.ts +153 -0
- package/src/self-health/signatures.ts +6 -0
- package/src/self-implement/blocked-draft-policy.ts +20 -0
- package/src/self-implement/classify-shadow.ts +3 -7
- package/src/self-implement/device-first.ts +116 -0
- package/src/self-implement/doctor-visit.ts +257 -2
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/gate-baseline.ts +56 -17
- package/src/self-implement/gate-cli.ts +1 -0
- package/src/self-implement/gate-remote.ts +31 -20
- package/src/self-implement/goal-author-cli.ts +1 -0
- package/src/self-implement/goal-author-decompose.ts +534 -45
- package/src/self-implement/goal-author.ts +282 -7
- package/src/self-implement/goal-elaborate.ts +449 -0
- package/src/self-implement/goal-refine.ts +273 -0
- package/src/self-implement/graph-overlay-yaml.ts +14 -3
- package/src/self-implement/graph-variant.ts +40 -6
- package/src/self-implement/headless-elanous-driver.ts +19 -3
- package/src/self-implement/off-diff-evidence.ts +58 -6
- package/src/self-implement/orchestrator.ts +370 -62
- package/src/self-implement/pod-warm-pool-profile.ts +1 -0
- package/src/self-implement/pwa-import-graph.ts +1 -1
- package/src/self-implement/rework-budget-judgment.ts +5 -8
- package/src/self-implement/rework-policy.ts +30 -0
- package/src/self-implement/run-ledger.ts +108 -0
- package/src/self-implement/run-stop.ts +10 -1
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/running-runs.ts +29 -1
- package/src/self-implement/scope-adjacency.ts +115 -0
- package/src/self-implement/seams.ts +187 -28
- package/src/self-implement/stop-autoheal.ts +87 -1
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/session-runtime/index.ts +1 -1
- package/src/setup/llm-env-detect.ts +5 -0
- package/src/shell-env-bootstrap.ts +11 -4
- package/src/skills/runner.ts +4 -2
- package/src/skills/tools/agent.ts +408 -40
- package/src/skills/tools/msg-ask.ts +100 -0
- package/src/skills/tools/ops-ledger.ts +2 -1
- package/src/skills/tools/schedule-wakeup.ts +23 -4
- package/src/skills/tools/shell.ts +61 -6
- package/src/skills/tools/terminal-modal-inject.ts +1 -2
- package/src/skills/tools/terminal-modal.ts +10 -8
- package/src/skills/url-router.ts +41 -1
- package/src/smart-wizard/run.ts +66 -0
- package/src/status/bar.ts +34 -6
- package/src/status/status-provider-info.ts +37 -6
- package/src/steward/hold-hygiene.ts +24 -6
- package/src/steward/rejudge-tick.ts +10 -2
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/steward/triage-budget.ts +194 -0
- package/src/steward/triage.ts +20 -3
- package/src/task-agent/actions.ts +72 -2
- package/src/task-agent/green-sweep.ts +4 -0
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +182 -10
- package/src/task-agent/mission.ts +30 -0
- package/src/task-agent/parent-host.ts +123 -3
- package/src/task-agent/rejudge-on-head.ts +15 -0
- package/src/task-agent/repair-cycle.ts +138 -32
- package/src/task-agent/repair-next-move.ts +31 -3
- package/src/task-agent/repair-outcomes.ts +29 -0
- package/src/task-agent/repair-worker.ts +14 -4
- package/src/task-agent/shadow.ts +48 -4
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +525 -57
- package/src/task-agent/task-parent-role-llm.ts +36 -0
- package/src/task-agent/tier-split.ts +196 -0
- package/src/task-agent/work-order-build.ts +2 -0
- package/src/task-agent/work-order.ts +3 -2
- package/src/task-orchestrator/generator-prompt.ts +60 -11
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +42 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +130 -11
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
- package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
- package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +19 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
- package/src/task-orchestrator/surfaces/pod-provenance.ts +82 -0
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
- package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +7 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +521 -40
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +759 -50
- package/src/task-orchestrator/surfaces/self-implement.ts +65 -2
- package/src/tool-runtime/index.ts +15 -2
- package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
- package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
- package/src/tty-foreground.ts +155 -0
- package/src/tui/README.md +2 -0
- package/src/tui/chat/background-job-status.ts +54 -0
- package/src/tui/chat/goal-interim.ts +173 -0
- package/src/tui/chat/log-view.ts +267 -0
- package/src/tui/chat/self-health-status.ts +232 -0
- package/src/tui/entry.ts +695 -0
- package/src/tui/input/composer.ts +151 -0
- package/src/tui/seat-mail-boot.ts +59 -0
- package/src/tui/slash/compact-command.ts +7 -0
- package/src/tui/slash/compact.ts +94 -0
- package/src/tui/slash/new-session.ts +35 -0
- package/src/tui/slash/resume.ts +75 -0
- package/src/tui-client/local-chat-turn.ts +282 -0
- package/src/tui-client/local-harness-command.ts +63 -0
- package/src/tui-client/local-provider-command.ts +184 -0
- package/src/tui-client/next-dashboard-runtime.ts +8 -0
- package/src/tui-client/next-loop-command.ts +70 -0
- package/src/tui-client/next-wakeup.ts +191 -0
- package/src/tui.ts +37 -0
- package/src/ui/modal-adapter.ts +2 -2
- package/src/user-config.ts +4 -2
- package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
- package/src/ux-sim/cli-config-audit-device.ts +121 -0
- package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
- package/src/ux-sim/event-monkey.ts +379 -0
- package/src/ux-sim/fake-bin.ts +87 -7
- package/src/ux-sim/fake-herdr.ts +238 -0
- package/src/ux-sim/fake-llm.ts +111 -6
- package/src/ux-sim/fault-glm-no-credential.ts +116 -0
- package/src/ux-sim/fault-nextmove-dispatch.ts +367 -0
- package/src/ux-sim/fault-pod-codex-503.ts +211 -0
- package/src/ux-sim/fault-scope-escape-lift.ts +216 -0
- package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
- package/src/ux-sim/goal-device.ts +619 -0
- package/src/ux-sim/in-process-cli.ts +22 -5
- package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
- package/src/ux-sim/mock-authoring-first-run.ts +97 -0
- package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
- package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
- package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
- package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
- package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
- package/src/ux-sim/onboarding-first-run-device.ts +269 -0
- package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
- package/src/ux-sim/subagent-tools-device.ts +81 -0
- package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
- package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
- package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
- package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
- package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
- package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2987-30cf9c2eb7b8bdd5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
- package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
- package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
- package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
- package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
- package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
- package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
- package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
- package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
- package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +0 -287
- package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
- package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
- package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
- package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
- package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-df3445b207ceceab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
- package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
- /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
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
//
|
|
126
|
-
|
|
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
|
-
|
|
226
|
-
const ready =
|
|
227
|
-
|
|
228
|
-
|
|
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.
|
|
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
|
|
270
|
-
if (!
|
|
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)
|
|
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 {
|
|
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.
|
|
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
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
813
|
+
const { action, workerId, gateFile } = parseWarmPodProviderArgs(process.argv.slice(2));
|
|
382
814
|
const file = process.env.ELANOUS_WARM_POD_PROVIDER_CONFIG;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
}
|