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
|
@@ -18,6 +18,8 @@ import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroun
|
|
|
18
18
|
import { POD_CREDENTIAL_GROK_PATH, POD_CREDENTIAL_GITHUB_PATH, POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV, installationRepositories } from '../../nexus/api/pod-credential-api.js';
|
|
19
19
|
import { coalescedInstallationCredential } from '../../auth/github-app-token.js';
|
|
20
20
|
import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
|
|
21
|
+
import { isVerifiedOpenGatewayModel } from '../../llm/opengateway-models.js';
|
|
22
|
+
import { configuredOpenGatewayKeys } from '../../llm/provider-credentials.js';
|
|
21
23
|
import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
|
|
22
24
|
import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
|
|
23
25
|
import { readPodRepairArtifact } from './pod-repair-commit.js';
|
|
@@ -28,12 +30,18 @@ import { controlInboxEnv } from '../../harness/control-inbox.js';
|
|
|
28
30
|
import { finishPodFragment, writePodFragment } from '../../harness/self-send-target.js';
|
|
29
31
|
import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
|
|
30
32
|
import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
|
|
31
|
-
import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
33
|
+
import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
|
|
34
|
+
import { runSuffixKey } from '../../self-implement/salvage-branches.js';
|
|
32
35
|
import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
|
|
33
36
|
import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
34
37
|
import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
|
|
35
38
|
import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
|
|
36
39
|
import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
|
|
40
|
+
import { assessSiWarmReply, decideSiWarmNextStep, type SiWarmCommand, type SiWarmReply,
|
|
41
|
+
type SiWarmReturnedBytes } from './pod-si-warm-protocol.js';
|
|
42
|
+
import { cleanAndReturnChildPod, type ChildPodReuseOperations } from './pod-child-reuse.js';
|
|
43
|
+
import { executeSiWarmTransport, type SiWarmFixedRunner,
|
|
44
|
+
type SiWarmPodIdentity } from './pod-si-warm-transport.js';
|
|
37
45
|
|
|
38
46
|
export const POD_CONTROL_INBOX_DIR = '/tmp/elanous-control.inbox';
|
|
39
47
|
/** Pod Job 수명 상한(`activeDeadlineSeconds`) — 대표 2026-09-26 90분 → 180분(90분에 `DeadlineExceeded` 로 죽은 런 둘). */
|
|
@@ -56,6 +64,9 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
|
|
|
56
64
|
// POD-DIET (10-06 03:47~05:18 · node-b goal Pods · 89 one-minute samples · 42 Pods): per-Pod observed max median 4.6Gi ·
|
|
57
65
|
// p75 6.2Gi · p90 11.7Gi — 23/42 went above the old 4Gi request, so the scheduler over-packed. 6Gi ≈ p75; limits stay.
|
|
58
66
|
// Node allocatable ≈ 343Gi ≫ 25 × 6Gi. One-minute samples can miss the true peak (lower bound).
|
|
67
|
+
import { POD_LAUNCH_FINISHED_EVENT, POD_LAUNCH_STARTED_EVENT, provenanceRowFromAnnotations, siTaskPodProvenance } from './pod-launch-history.js';
|
|
68
|
+
import { podProvenanceMetadata, type PodProvenanceMetadata } from './pod-provenance.js';
|
|
69
|
+
import { packageVersion } from '../../version/code-revision.js';
|
|
59
70
|
import { podWorkLabels, type PodWorkIdentity, type PodPlacement, type PodPlacementKind, type PodPoolMember, type PodPoolScheduler } from './pod-pool.js';
|
|
60
71
|
export { POD_CHILD_REQUESTS } from './pod-lease.js';
|
|
61
72
|
import { measurePoolLease, recommendConcurrency, POD_CHILD_REQUESTS, POD_SI_ADMISSION_CAP_BYTES, POD_HOST_LEASE_ANNOTATION, POD_JOB_TTL_AFTER_FINISHED_SECONDS, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
|
|
@@ -68,7 +79,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
|
68
79
|
import { findGitDir } from '../../git-fs/locate.js';
|
|
69
80
|
import { hostLeaseBaseDir } from '../../pod-lease/host-lease.js';
|
|
70
81
|
import { acquireLockAsync } from '../../storage/file-lock.js';
|
|
71
|
-
import { debug } from '../../debug/log.js';
|
|
82
|
+
import { debug, redactSecretText } from '../../debug/log.js';
|
|
72
83
|
import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-dev/graph-journey-nodes.js';
|
|
73
84
|
import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
|
|
74
85
|
import { selectLiveDetail } from '../../live/detail-switch.js';
|
|
@@ -94,7 +105,7 @@ import { ZAI_POD_EGRESS_LABEL, checkZaiPodSecret, countActiveZaiJobs, decideZaiP
|
|
|
94
105
|
import { ZAI_CODING_DEFAULT_MODEL, ZAI_OPENROUTER_FALLBACK_MODEL, zaiSettingsFromConfig, type ZaiCodingPlanSettings } from '../../llm/zai-coding-plan.js';
|
|
95
106
|
import { normalizePlanningFallbackReason, selectEnhanceLlm } from '../../prompt-enhance/enhance.js';
|
|
96
107
|
import { inferProviderFromModel } from '../../llm.js';
|
|
97
|
-
import { classifyPodExit, extractPodFailureReason, podTerminalRow, type PodExitClass } from './pod-failure-reason.js';
|
|
108
|
+
import { classifyPodExit, extractPodFailureReason, podChildFailureAttribution, podTerminalRow, POD_TERMINATION_JOB_JSONPATH, POD_TERMINATION_PODS_JSONPATH, readPodTermination, type PodExitClass, type PodTermination } from './pod-failure-reason.js';
|
|
98
109
|
import { kubectlTransientReason, retryKubectlTransient } from './kubectl-transient-retry.js';
|
|
99
110
|
import { OLD_DOOR_STAMP_ENV } from '../../self-dev/old-door.js';
|
|
100
111
|
import { resolveDefaultSourceHead, seedMirrorOnce, type MirrorSeedResult } from './pod-mirror-seed.js';
|
|
@@ -102,7 +113,24 @@ import { decideWarmReuse, type WarmReuseEvidenceByConsumer, type WarmReuseSwitch
|
|
|
102
113
|
import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
|
|
103
114
|
import type { WarmPodTransportResult } from './pod-warm-worker-pod.js';
|
|
104
115
|
|
|
105
|
-
export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
116
|
+
export type Kubectl = (args: readonly string[], input?: string, timeoutMs?: number) => { status: number | null; stdout: string; stderr: string };
|
|
117
|
+
|
|
118
|
+
/** TC's si-v1 transport supplies this receipt. No lease is considered reusable from a Job exit alone. */
|
|
119
|
+
export interface SiWarmChildAttempt {
|
|
120
|
+
leaseFenced: boolean;
|
|
121
|
+
command: SiWarmCommand | null;
|
|
122
|
+
/** The consumer executes this fenced transport. A provider cannot assert its own reply verdict. */
|
|
123
|
+
transport: { pod: SiWarmPodIdentity; runner: SiWarmFixedRunner; now?: () => number };
|
|
124
|
+
payload: Uint8Array;
|
|
125
|
+
credentialBundle: Uint8Array;
|
|
126
|
+
/** Collect verified ledger/artifact bytes into the existing host stores. */
|
|
127
|
+
adopt: (command: SiWarmCommand, reply: SiWarmReply, bytes: SiWarmReturnedBytes) => Promise<void>;
|
|
128
|
+
cleanup: ChildPodReuseOperations;
|
|
129
|
+
/** The provider fences a worker even when a transport failure hides its command receipt. */
|
|
130
|
+
quarantineUnknown: (reason: string) => Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
type OpenPrByBranch = { status: 'found'; prNumber: number; prUrl: string }
|
|
133
|
+
| { status: 'not-found' | 'failed'; reason: string };
|
|
106
134
|
|
|
107
135
|
/** TC supplies the validated candidate/lease transport. UX only forwards its request command. */
|
|
108
136
|
export type WarmPodRequestHook = (request: { command: WarmWorkerCommand }) => Promise<WarmPodTransportResult> | WarmPodTransportResult;
|
|
@@ -118,6 +146,13 @@ const APP_GH_AUTH_COMMAND = `bun -e '${APP_GH_AUTH_SCRIPT}'`;
|
|
|
118
146
|
const APP_GH_EXEC = `unset GH_TOKEN GITHUB_TOKEN; GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}" exec "$@"`;
|
|
119
147
|
|
|
120
148
|
export interface PodSpawnOptions {
|
|
149
|
+
/** si-v1 living worker consumer. Absent means the existing one-shot Job path. */
|
|
150
|
+
siWarmAttempt?: (input: Parameters<SelfImplementJobSpawn>[0]) => Promise<SiWarmChildAttempt | null>;
|
|
151
|
+
/** POD-REUSE-SI-PROVIDER-WIRE: must apply cold-path host regate, ACP, terminal and repair checks before warm adoption. */
|
|
152
|
+
siWarmHostPostprocess?: (result: SelfImplementJobDone, attempt: SiWarmChildAttempt,
|
|
153
|
+
input: Parameters<SelfImplementJobSpawn>[0]) => Promise<SelfImplementJobDone>;
|
|
154
|
+
/** Injected test ledger; production writes to the existing run ledger. */
|
|
155
|
+
siWarmLedger?: (entry: RunLedgerEntry) => void;
|
|
121
156
|
/** 이미지 판(라벨 elanous.commit) — 주입(시험·감독기가 이미 잰 값). 없으면 docker 로 잰다. */
|
|
122
157
|
imageCommit?: string | null;
|
|
123
158
|
/** codex 계정 이름(`~/.elanous/auth.json` 의 `openai-codex:<이름>`). 기본 team. */
|
|
@@ -169,6 +204,8 @@ export interface PodSpawnOptions {
|
|
|
169
204
|
authorConfig?: () => UserConfig;
|
|
170
205
|
/** Host fallback chain seam; a Pod has no access to the host config. */
|
|
171
206
|
fallbackConfig?: () => UserConfig;
|
|
207
|
+
/** Host OpenGateway key config seam; unit tests inject isolated config without bypassing production reads. */
|
|
208
|
+
openGatewayConfig?: () => UserConfig;
|
|
172
209
|
/** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
|
|
173
210
|
source?: PodSource;
|
|
174
211
|
/** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
|
|
@@ -205,6 +242,8 @@ export interface PodSpawnOptions {
|
|
|
205
242
|
taskAgentLiveMoves?: () => Promise<ReadonlySet<string>> | ReadonlySet<string>;
|
|
206
243
|
/** PR comment for host-regate failures that never reached runHostRegate (injectable for tests). */
|
|
207
244
|
ghComment?: (prNumber: number, body: string) => void;
|
|
245
|
+
/** Read-only host gh lookup; injected fixtures can stand in for gh without creating or closing a PR. */
|
|
246
|
+
lookupOpenPrByBranch?: (branch: string, repoUrl: string) => Promise<OpenPrByBranch> | OpenPrByBranch;
|
|
208
247
|
draftResidue?: (record: PrTerminalRecord, ctx: DraftResidueContext) => Promise<void> | void;
|
|
209
248
|
/** 🔑 Pod 필수 스킬의 키(.env)를 이 런의 Secret 으로 넘긴다 — 명시 opt-in(유료 크레딧을 쓴다). */
|
|
210
249
|
skillEnv?: boolean;
|
|
@@ -397,6 +436,11 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
|
|
|
397
436
|
// ⑴ 잠금을 기다리는 사이 동료 런이 갱신했으면 그대로 쓴다 — 두 번째 갱신이 회전된 토큰으로 실패하는 자리.
|
|
398
437
|
const first = reread();
|
|
399
438
|
if (!('expiringH' in first)) { observe('refresh-skipped-already-fresh', null); return first; }
|
|
439
|
+
// The lease can transfer while an account lock is queued. Recheck inside the
|
|
440
|
+
// critical section, immediately before the only refresh-token operation.
|
|
441
|
+
let stillHolder = false;
|
|
442
|
+
try { stillHolder = await deps.isLeaseHolder(); } catch { stillHolder = false; }
|
|
443
|
+
if (!stillHolder) { observe('no-lease', null, 'lease-lost-before-refresh'); return null; }
|
|
400
444
|
let refreshed: Awaited<ReturnType<typeof deps.refresh>> | null = null;
|
|
401
445
|
let failReason = 'threw';
|
|
402
446
|
try { refreshed = await deps.refresh(account); }
|
|
@@ -430,6 +474,7 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
|
|
|
430
474
|
|
|
431
475
|
/** 기본 임대 판정 — 기존 HQ 펜스(`ledger-cli` 역할 · fail-open 없음)를 그대로 쓴다. */
|
|
432
476
|
async function defaultHqLeaseHolder(): Promise<boolean> {
|
|
477
|
+
if (process.env.ELANOUS_REMOTE_CODEX_ACCESS_ONLY === '1') return false;
|
|
433
478
|
const { hqFenceDecision } = await import('../../hq/hq.js');
|
|
434
479
|
return hqFenceDecision('ledger-cli').run;
|
|
435
480
|
}
|
|
@@ -649,20 +694,45 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
|
|
|
649
694
|
return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
|
|
650
695
|
}
|
|
651
696
|
|
|
652
|
-
/** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403)
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
697
|
+
/** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403).
|
|
698
|
+
* The global hook refuses main/tags/unknown refs and most deletes. With a runId, self-impl refs use the same six-hex
|
|
699
|
+
* run suffix as planned branch names; only that suffix and this run's disposable PR scratch create/delete pass.
|
|
700
|
+
* A same-card PR reuse push is the one exception: per-command Git config binds its ref, old head and card;
|
|
701
|
+
* the hook compares the Pod card and proves fast-forward ancestry before accepting it.
|
|
702
|
+
* salvage/* and bench arm refs keep their existing broader rules. A six-hex collision remains possible, so this is
|
|
703
|
+
* an accidental overwrite guard, not a security isolation boundary; child credential isolation is a separate slice. */
|
|
657
704
|
export const POD_GIT_HOOKS_DIR = '$HOME/.elanous-git-hooks';
|
|
658
|
-
export function podGitPushGuardScript(): string {
|
|
705
|
+
export function podGitPushGuardScript(runId?: string): string {
|
|
706
|
+
const runKey = runId ? runSuffixKey(runId) : null;
|
|
707
|
+
const zeroSha = '0'.repeat(40);
|
|
659
708
|
return [
|
|
660
709
|
`mkdir -p "${POD_GIT_HOOKS_DIR}" && cat > "${POD_GIT_HOOKS_DIR}/pre-push" <<'ELANOUS_PRE_PUSH'`,
|
|
661
710
|
'#!/bin/sh',
|
|
662
711
|
'while read -r lref lsha rref rsha; do',
|
|
663
|
-
' case "$lsha" in *[!0]*) ;; *)
|
|
712
|
+
' case "$lsha" in *[!0]*) ;; *)',
|
|
713
|
+
...(runKey ? [
|
|
714
|
+
` case "$rref" in refs/heads/self-impl/pr-create-r${runKey}-*) ;; *) echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;; esac ;;`,
|
|
715
|
+
] : [' echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;;']),
|
|
716
|
+
' esac',
|
|
664
717
|
' case "$rref" in',
|
|
665
|
-
|
|
718
|
+
...(runKey ? [
|
|
719
|
+
` refs/heads/self-impl/pr-create-r${runKey}-*)`,
|
|
720
|
+
` case "$lsha:$rsha" in ${zeroSha}:*|*:${zeroSha}) ;; *) echo "[pod] git-guard: refused scratch ref update $rref" >&2; exit 1 ;; esac ;;`,
|
|
721
|
+
` refs/heads/self-impl/*-r${runKey}|refs/heads/self-impl/*-r${runKey}-early) ;;`,
|
|
722
|
+
' refs/heads/self-impl/*)',
|
|
723
|
+
' reuse_ref=$(git config --get elanous.sameCellReuseRef 2>/dev/null || true)',
|
|
724
|
+
' reuse_head=$(git config --get elanous.sameCellReuseHead 2>/dev/null || true)',
|
|
725
|
+
' reuse_card=$(git config --get elanous.sameCellReuseCard 2>/dev/null || true)',
|
|
726
|
+
' if [ -z "$reuse_ref" ]; then echo "[pod] git-guard: refused push to $rref (another run)" >&2; exit 1; fi',
|
|
727
|
+
' if [ "$rref" != "$reuse_ref" ]; then echo "[pod] git-guard: refused same-cell ref-mismatch $rref" >&2; exit 1; fi',
|
|
728
|
+
' if [ -z "${ELANOUS_TASK_CARD_ID:-}" ] || [ "$reuse_card" != "$ELANOUS_TASK_CARD_ID" ]; then echo "[pod] git-guard: refused same-cell card-mismatch $rref" >&2; exit 1; fi',
|
|
729
|
+
' if [ -z "$reuse_head" ] || [ "$rsha" != "$reuse_head" ]; then echo "[pod] git-guard: refused same-cell remote-head-mismatch $rref" >&2; exit 1; fi',
|
|
730
|
+
' if ! git merge-base --is-ancestor "$rsha" "$lsha" 2>/dev/null; then echo "[pod] git-guard: refused same-cell non-fast-forward $rref" >&2; exit 1; fi',
|
|
731
|
+
' echo "[pod] git-guard: allowed same-cell fast-forward $rref" >&2 ;;',
|
|
732
|
+
] : [
|
|
733
|
+
...(runId ? [' refs/heads/self-impl/*) echo "[pod] git-guard: refused push to $rref (invalid run id)" >&2; exit 1 ;;'] : [' refs/heads/self-impl/*) ;;']),
|
|
734
|
+
]),
|
|
735
|
+
' refs/heads/salvage/*) ;;',
|
|
666
736
|
' refs/heads/bench/*/base) echo "[pod] git-guard: refused push to shared bench base $rref" >&2; exit 1 ;;',
|
|
667
737
|
' refs/heads/bench/*/*) ;;',
|
|
668
738
|
' *) echo "[pod] git-guard: refused push to $rref (only this run\'s branches)" >&2; exit 1 ;;',
|
|
@@ -963,7 +1033,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
|
|
|
963
1033
|
}
|
|
964
1034
|
|
|
965
1035
|
/** A named child provider decides the Pod provider; paid OpenRouter requires an explicit key and model. */
|
|
966
|
-
export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
|
|
1036
|
+
export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; openGatewayKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
|
|
967
1037
|
// 공백·빈 이름 = «미지정»(종전 기본 경로) — 유료 provider 로 흘러가지 않는다(codex 재리뷰 must-fix).
|
|
968
1038
|
if (!named?.trim()) return {};
|
|
969
1039
|
// ⭐ 목록은 한 곳(pod-child-providers.ts) — 발사 관문도 같은 상수·같은 문면으로 거부한다.
|
|
@@ -978,6 +1048,12 @@ export function podNamedChildProvider(named: string | undefined, credential: { g
|
|
|
978
1048
|
if (!childModel?.trim()) return { refuse: 'pod: openrouter 자식 모델 없음 — --child-llm-model 필요' };
|
|
979
1049
|
return { provider: 'openrouter' };
|
|
980
1050
|
}
|
|
1051
|
+
if (named === 'opengateway') {
|
|
1052
|
+
if (!credential.openGatewayKey) return { refuse: 'pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요' };
|
|
1053
|
+
if (!childModel?.trim()) return { refuse: 'pod: opengateway 자식 모델 없음 — --child-llm-model 필요' };
|
|
1054
|
+
if (!isVerifiedOpenGatewayModel(childModel.trim())) return { refuse: `pod: opengateway model not verified: ${childModel.trim()}` };
|
|
1055
|
+
return { provider: 'opengateway' };
|
|
1056
|
+
}
|
|
981
1057
|
// BEDROCK-PROVIDER ② — 자격은 클러스터의 이름 있는 Secret 에서 온다. 있나·키가 갖춰졌나는 Pod 발사 «전»(apply 0)에 판정한다(checkBedrockPodSecret).
|
|
982
1058
|
if (named === 'bedrock') return { provider: 'bedrock' };
|
|
983
1059
|
// ZAI-POD-CHILD — glm 도 같은 결: 자격은 클러스터의 이름 있는 ZAI Secret · 있나·동시 상한은 Pod 발사 «전»(apply 0)에 판정한다(pod-zai-child.ts).
|
|
@@ -1012,7 +1088,8 @@ export function podChildLlmArgs(options: Pick<PodSpawnOptions, 'provider' | 'chi
|
|
|
1012
1088
|
const provider = options.provider === 'openai-codex' && !options.childProviderExplicit ? undefined : options.provider;
|
|
1013
1089
|
if (!provider) return [];
|
|
1014
1090
|
const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : provider === 'glm' ? ZAI_CODING_DEFAULT_MODEL : undefined);
|
|
1015
|
-
if (provider === 'openrouter' && !model) throw new Error(
|
|
1091
|
+
if ((provider === 'openrouter' || provider === 'opengateway') && !model) throw new Error(`pod: ${provider} 자식 모델 없음 — --child-llm-model 필요`);
|
|
1092
|
+
if (provider === 'opengateway' && !isVerifiedOpenGatewayModel(model!)) throw new Error(`pod: opengateway model not verified: ${model}`);
|
|
1016
1093
|
// `self implement` registers no --child-llm-effort — passing it kills the Pod at argument parsing, so effort stays host-only.
|
|
1017
1094
|
return ['--child-llm-provider', provider, ...(model ? ['--child-llm-model', model] : [])];
|
|
1018
1095
|
}
|
|
@@ -1069,7 +1146,7 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
|
|
|
1069
1146
|
return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
|
|
1070
1147
|
}
|
|
1071
1148
|
|
|
1072
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; boundPrNumber?: number; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec; /** Codex Job may use the same named Secret only for an explicitly admitted ZAI fallback. */ zaiFallback?: ZaiPodSpec; fallbackChain?: { steps: readonly FallbackStep[]; source: 'host' | 'default' }; /** TA-POD-WARMBOOT-PARITY — the child's final line (podResultMarkerShellLine), just before `exit $rc`. */ resultMarkerLine?: string }): Record<string, unknown> {
|
|
1149
|
+
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; boundPrNumber?: number; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec; /** Codex Job may use the same named Secret only for an explicitly admitted ZAI fallback. */ zaiFallback?: ZaiPodSpec; fallbackChain?: { steps: readonly FallbackStep[]; source: 'host' | 'default' }; /** TA-POD-WARMBOOT-PARITY — the child's final line (podResultMarkerShellLine), just before `exit $rc`. */ resultMarkerLine?: string; /** POD-LAUNCH-PROVENANCE — the shared provenance set (labels ⊕ annotations ⊕ child env) for this si-task Job. */ provenance?: PodProvenanceMetadata }): Record<string, unknown> {
|
|
1073
1150
|
if (o.repairCommitOnly && (o.source?.kind !== 'pr' || o.source.number !== o.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(o.repairCommitOnly.baseHead) || o.args.includes('--open-pr') || o.args.includes('--merge-by-host') || o.args.includes('--auto-merge'))) {
|
|
1074
1151
|
throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
|
|
1075
1152
|
}
|
|
@@ -1078,6 +1155,8 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1078
1155
|
const childModelIndex = o.args.indexOf('--child-llm-model');
|
|
1079
1156
|
const openrouterModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'openrouter' && childModelIndex >= 0
|
|
1080
1157
|
? o.args[childModelIndex + 1] : undefined;
|
|
1158
|
+
const openGatewayModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'opengateway' && childModelIndex >= 0
|
|
1159
|
+
? o.args[childModelIndex + 1] : undefined;
|
|
1081
1160
|
// POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
|
|
1082
1161
|
const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
|
|
1083
1162
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
@@ -1101,7 +1180,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1101
1180
|
'mkdir -p "$HOME/.elanous" && install -m 600 /creds/llm-fallback-config.json "$HOME/.elanous/config.json" || exit 6',
|
|
1102
1181
|
`echo "ELANOUS_POD_FALLBACK_CHAIN source=${o.fallbackChain.source} steps=${o.fallbackChain.steps.join(',')}"`,
|
|
1103
1182
|
] : []),
|
|
1104
|
-
|
|
1183
|
+
openGatewayModel
|
|
1184
|
+
? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=opengateway'
|
|
1185
|
+
: openrouterModel
|
|
1105
1186
|
? o.reviewerCodexAuth
|
|
1106
1187
|
? 'mkdir -p ~/.elanous ~/.codex && cp /creds/elanous-auth.json ~/.elanous/auth.json && cp /creds/codex-auth.json ~/.codex/auth.json && chmod 600 ~/.elanous/auth.json ~/.codex/auth.json && export ELANOUS_LLM_PROVIDER=openrouter'
|
|
1107
1188
|
: 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=openrouter'
|
|
@@ -1120,7 +1201,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1120
1201
|
// 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
|
|
1121
1202
|
...(o.skillEnvs?.length ? [`for n in ${o.skillEnvs.join(' ')}; do [ -d ~/.claude/skills/$n ] && install -m 600 /creds/skillenv-$n ~/.claude/skills/$n/.env; done; echo "[pod] skill env: ${o.skillEnvs.join(',')}"`] : []),
|
|
1122
1203
|
'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
|
|
1123
|
-
podGitPushGuardScript(),
|
|
1204
|
+
podGitPushGuardScript(o.runId),
|
|
1124
1205
|
'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
|
|
1125
1206
|
// SI-POD-STAGE-MARKERS — Pod 시계 단계 표지(`ELANOUS_POD_STAGE <stage> <start|end> <epoch초>`): 호스트가 Job 로그에서 읽어
|
|
1126
1207
|
// pod.lifecycle stages 의 clone · mirror · run 을 채운다. 실패한 단계는 끝 표지가 없다(→ missing · 지어내지 않는다).
|
|
@@ -1132,6 +1213,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1132
1213
|
// Record that fact before the main command, without treating clone completion as install completion.
|
|
1133
1214
|
`printf 'ELANOUS_POD_DEPS skipped %s image-prepared-no-install\\n' "$(date +%s)" || true`,
|
|
1134
1215
|
...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
|
|
1216
|
+
// PR checkout and, when requested, the repair HEAD pin have passed before this child starts.
|
|
1217
|
+
// A supervisor input may continue the repair, but cannot launch its source again.
|
|
1218
|
+
...(o.source?.kind === 'pr' ? ['export ELANOUS_POD_VERIFIED_PR_SOURCE=1'] : []),
|
|
1135
1219
|
...(goalPath ? [
|
|
1136
1220
|
`mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
|
|
1137
1221
|
] : []),
|
|
@@ -1150,7 +1234,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1150
1234
|
// AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
|
|
1151
1235
|
siTaskPodStageShell('run', 'start'),
|
|
1152
1236
|
o.repairCommitOnly
|
|
1153
|
-
? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; if
|
|
1237
|
+
? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; if elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox"; then rc=0; elif [ "$rc" -eq 0 ]; then rc=8; fi`
|
|
1154
1238
|
: o.authorSentence
|
|
1155
1239
|
? `echo "ELANOUS_AUTHOR_ON_POD started host=$(hostname) at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; author_t0=$(date +%s); ${o.authorGrade === 'lite' || o.authorGrade === 'full' ? `ELANOUS_AUTHOR_GRADE=${o.authorGrade} ` : ''}elanous harness say --substrate local --json${quotedAsk}${quotedChildLlm} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
|
|
1156
1240
|
: goalPath
|
|
@@ -1225,15 +1309,15 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1225
1309
|
return {
|
|
1226
1310
|
apiVersion: 'batch/v1',
|
|
1227
1311
|
kind: 'Job',
|
|
1228
|
-
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
|
|
1229
|
-
...((o.execution || o.hostLeaseAdmitted) ? { annotations: { ...(o.execution ? { [POD_EXECUTION_KEY_ANNOTATION]: o.execution.key, [POD_ATTEMPT_ANNOTATION]: String(o.execution.attempt) } : {}), ...(o.hostLeaseAdmitted ? { [POD_HOST_LEASE_ANNOTATION]: 'true' } : {}) } } : {}) },
|
|
1312
|
+
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}), ...o.provenance?.labels },
|
|
1313
|
+
...((o.execution || o.hostLeaseAdmitted || o.provenance) ? { annotations: { ...o.provenance?.annotations, ...(o.execution ? { [POD_EXECUTION_KEY_ANNOTATION]: o.execution.key, [POD_ATTEMPT_ANNOTATION]: String(o.execution.attempt) } : {}), ...(o.hostLeaseAdmitted ? { [POD_HOST_LEASE_ANNOTATION]: 'true' } : {}) } } : {}) },
|
|
1230
1314
|
spec: {
|
|
1231
1315
|
backoffLimit: 0,
|
|
1232
1316
|
ttlSecondsAfterFinished: POD_JOB_TTL_AFTER_FINISHED_SECONDS,
|
|
1233
1317
|
activeDeadlineSeconds: o.deadlineSeconds,
|
|
1234
1318
|
template: {
|
|
1235
1319
|
// ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
|
|
1236
|
-
metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
|
|
1320
|
+
metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}), ...o.provenance?.labels }, ...(o.provenance ? { annotations: o.provenance.annotations } : {}) },
|
|
1237
1321
|
spec: {
|
|
1238
1322
|
restartPolicy: 'Never',
|
|
1239
1323
|
securityContext: { runAsUser: 1000, fsGroup: 1000 },
|
|
@@ -1260,6 +1344,8 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1260
1344
|
{ name: 'ELANOUS_POD_NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } },
|
|
1261
1345
|
...(o.hostId ? [{ name: 'ELANOUS_HOST_ID', value: o.hostId }] : []),
|
|
1262
1346
|
...(o.imageCommit ? [{ name: 'ELANOUS_IMAGE_COMMIT', value: o.imageCommit }] : []),
|
|
1347
|
+
// POD-LAUNCH-PROVENANCE — the child knows who launched it, why and when (ELANOUS_POD_<key> · no secrets).
|
|
1348
|
+
...(o.provenance?.env ?? []),
|
|
1263
1349
|
// ⭐ 런 계약(graph-run-contract.ts) — 이 칸이 «실제로» Pod 이고, 부모가 정한 계약도 Pod 다. 안의 그래프가 노드마다 대조한다.
|
|
1264
1350
|
{ name: ACTUAL_SUBSTRATE_ENV, value: 'pod' }, { name: RUN_CONTRACT_ENV, value: carryRunContract({ substrate: 'pod' }) },
|
|
1265
1351
|
...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
|
|
@@ -1291,6 +1377,93 @@ function defaultGhComment(prNumber: number, body: string): void {
|
|
|
1291
1377
|
if (r.status !== 0) throw new Error((r.stderr || r.error?.message || `gh exited ${r.status}`).trim().slice(0, 200));
|
|
1292
1378
|
}
|
|
1293
1379
|
|
|
1380
|
+
function defaultLookupOpenPrByBranch(branch: string, repoUrl: string): OpenPrByBranch {
|
|
1381
|
+
const repo = /^https:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/u.exec(repoUrl)?.[1];
|
|
1382
|
+
if (!repo) return { status: 'failed', reason: 'repository-unrecognized' };
|
|
1383
|
+
if (!branch || !/^[A-Za-z0-9._/-]+$/u.test(branch) || branch.includes('..')) return { status: 'failed', reason: 'branch-invalid' };
|
|
1384
|
+
const result = spawnSync('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--head', branch,
|
|
1385
|
+
'--json', 'number,url,headRefName,isCrossRepository', '--limit', '100'], { encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576 });
|
|
1386
|
+
if (result.status !== 0) return { status: 'failed', reason: result.error ? 'gh-list-unavailable' : `gh-list-rc-${result.status ?? 'null'}` };
|
|
1387
|
+
let rows: unknown;
|
|
1388
|
+
try { rows = JSON.parse(result.stdout); } catch { return { status: 'failed', reason: 'gh-json-invalid' }; }
|
|
1389
|
+
if (!Array.isArray(rows)) return { status: 'failed', reason: 'gh-json-invalid' };
|
|
1390
|
+
if (rows.some((row) => row === null || typeof row !== 'object'
|
|
1391
|
+
|| typeof (row as { number?: unknown }).number !== 'number'
|
|
1392
|
+
|| !Number.isSafeInteger((row as { number: number }).number)
|
|
1393
|
+
|| typeof (row as { url?: unknown }).url !== 'string'
|
|
1394
|
+
|| typeof (row as { headRefName?: unknown }).headRefName !== 'string'
|
|
1395
|
+
|| typeof (row as { isCrossRepository?: unknown }).isCrossRepository !== 'boolean'))
|
|
1396
|
+
return { status: 'failed', reason: 'gh-json-invalid' };
|
|
1397
|
+
if (rows.length >= 100) return { status: 'failed', reason: 'gh-result-limit-reached' };
|
|
1398
|
+
const matching = rows.filter((row): row is { number: number; url: string; headRefName: string; isCrossRepository: false } => {
|
|
1399
|
+
if (row === null || typeof row !== 'object') return false;
|
|
1400
|
+
const candidate = row as { number?: unknown; url?: unknown; headRefName?: unknown; isCrossRepository?: unknown };
|
|
1401
|
+
return candidate.headRefName === branch && candidate.isCrossRepository === false && typeof candidate.number === 'number'
|
|
1402
|
+
&& Number.isSafeInteger(candidate.number) && candidate.number > 0 && typeof candidate.url === 'string';
|
|
1403
|
+
});
|
|
1404
|
+
if (matching.length === 0) return { status: 'not-found', reason: rows.some((row) =>
|
|
1405
|
+
(row as { headRefName: string }).headRefName === branch && (row as { isCrossRepository: boolean }).isCrossRepository)
|
|
1406
|
+
? 'cross-repository-only' : 'no-open-pr-for-branch' };
|
|
1407
|
+
if (matching.length !== 1) return { status: 'failed', reason: 'ambiguous-open-pr-for-branch' };
|
|
1408
|
+
const row = matching[0]!;
|
|
1409
|
+
if (row.url.replace(/\/$/u, '').toLowerCase() !== `https://github.com/${repo}/pull/${row.number}`.toLowerCase())
|
|
1410
|
+
return { status: 'failed', reason: 'pr-url-mismatch' };
|
|
1411
|
+
return { status: 'found', prNumber: row.number, prUrl: row.url };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
type PodImageObservation = { podName: string | null; podUid: string | null; podCreatedAt: string | null;
|
|
1415
|
+
imageID: string | null; digest: string | null; digestUnavailable: string | null };
|
|
1416
|
+
|
|
1417
|
+
/** Read the newest Pod owned by this Job. A stale replacement must never be named as the final Pod. */
|
|
1418
|
+
function readJobPodImage(kubectl: Kubectl, namespace: string, job: string, jobUid?: string): PodImageObservation {
|
|
1419
|
+
const unavailable = (reason: string, podName: string | null = null, imageID: string | null = null,
|
|
1420
|
+
podUid: string | null = null, podCreatedAt: string | null = null): PodImageObservation =>
|
|
1421
|
+
({ podName, podUid, podCreatedAt, imageID, digest: null, digestUnavailable: reason });
|
|
1422
|
+
let result: ReturnType<Kubectl>;
|
|
1423
|
+
try { result = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'json', '--request-timeout=5s']); }
|
|
1424
|
+
catch { return unavailable('pod-status-threw'); }
|
|
1425
|
+
if (result.status !== 0) return unavailable(`pod-status-rc-${result.status ?? 'null'}`);
|
|
1426
|
+
let rows: unknown;
|
|
1427
|
+
try { rows = (JSON.parse(result.stdout) as { items?: unknown }).items; }
|
|
1428
|
+
catch { return unavailable('pod-status-invalid-json'); }
|
|
1429
|
+
if (!Array.isArray(rows)) return unavailable('pod-status-invalid-items');
|
|
1430
|
+
const owned = rows.filter((row): row is {
|
|
1431
|
+
metadata: { name: string; uid?: string; creationTimestamp?: string; ownerReferences: Array<{ kind: string; name: string; uid?: string }> };
|
|
1432
|
+
status?: { containerStatuses?: Array<{ name?: string; imageID?: string }> };
|
|
1433
|
+
} => {
|
|
1434
|
+
if (!row || typeof row !== 'object') return false;
|
|
1435
|
+
const meta = (row as { metadata?: unknown }).metadata;
|
|
1436
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
1437
|
+
const m = meta as { name?: unknown; ownerReferences?: unknown };
|
|
1438
|
+
if (typeof m.name !== 'string' || !Array.isArray(m.ownerReferences)) return false;
|
|
1439
|
+
return m.ownerReferences.some((owner: unknown) => !!owner && typeof owner === 'object'
|
|
1440
|
+
&& (owner as { kind?: unknown }).kind === 'Job' && (owner as { name?: unknown }).name === job
|
|
1441
|
+
&& (!jobUid || (owner as { uid?: unknown }).uid === jobUid));
|
|
1442
|
+
}).sort((a, b) => {
|
|
1443
|
+
const at = Date.parse(a.metadata.creationTimestamp ?? '') || 0;
|
|
1444
|
+
const bt = Date.parse(b.metadata.creationTimestamp ?? '') || 0;
|
|
1445
|
+
return bt - at || b.metadata.name.localeCompare(a.metadata.name);
|
|
1446
|
+
});
|
|
1447
|
+
const pod = owned[0];
|
|
1448
|
+
if (!pod) return unavailable(rows.length ? 'no-job-owned-pod' : 'pod-not-created');
|
|
1449
|
+
const podUid = typeof pod.metadata.uid === 'string' ? pod.metadata.uid : null;
|
|
1450
|
+
const podCreatedAt = typeof pod.metadata.creationTimestamp === 'string' ? pod.metadata.creationTimestamp : null;
|
|
1451
|
+
const child = pod.status?.containerStatuses?.find((entry) => entry.name === 'child');
|
|
1452
|
+
if (!child) return unavailable('child-container-not-started', pod.metadata.name, null, podUid, podCreatedAt);
|
|
1453
|
+
const imageID = typeof child.imageID === 'string' ? child.imageID : null;
|
|
1454
|
+
if (!imageID?.trim()) return unavailable('image-id-missing', pod.metadata.name, imageID, podUid, podCreatedAt);
|
|
1455
|
+
const digest = /(?:^|[@/])(sha256:[0-9a-f]{64})$/iu.exec(imageID.trim())?.[1]?.toLowerCase() ?? null;
|
|
1456
|
+
return { podName: pod.metadata.name, podUid, podCreatedAt, imageID, digest, digestUnavailable: digest ? null : 'nonstandard-image-id' };
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/** A repair Pod succeeds only with a verified result/bundle, even if its child gate exited 1. */
|
|
1460
|
+
export function repairPodExitCode(input: { state: string; repairCommitOnly: boolean; repairArtifactReady: boolean;
|
|
1461
|
+
disposition?: { stage?: string; ok?: boolean } }): 0 | 1 {
|
|
1462
|
+
if (input.state !== 'complete') return 1;
|
|
1463
|
+
if (input.repairCommitOnly) return input.repairArtifactReady ? 0 : 1;
|
|
1464
|
+
return input.disposition?.stage !== 'host-regate-failed' && input.disposition?.ok !== false ? 0 : 1;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1294
1467
|
export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
|
|
1295
1468
|
if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
|
|
1296
1469
|
throw new Error('repair commit-only requires a matching PR source and exact head');
|
|
@@ -1306,10 +1479,290 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1306
1479
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
1307
1480
|
const hostSupervised = options.hostSupervised ?? true;
|
|
1308
1481
|
const resultMarkerOn = options.resultMarker ?? !options.kubectl;
|
|
1482
|
+
const writeWarmLedger = options.siWarmLedger
|
|
1483
|
+
?? ((entry: RunLedgerEntry) => appendRunLedgerEntry(entry, runLedgerDir(env.ELANOUS_STATE_DIR)));
|
|
1309
1484
|
return (input) => {
|
|
1310
1485
|
if (options.repairCommitOnly && (input.autoMerge || input.openPr || input.base !== `pr-${options.repairCommitOnly.pr}`)) {
|
|
1311
1486
|
throw new Error('repair commit-only refuses auto merge, new PR, or a different base');
|
|
1312
1487
|
}
|
|
1488
|
+
if (options.siWarmAttempt && !options.siWarmHostPostprocess) {
|
|
1489
|
+
throw new Error('si-v1 warm execution requires cold-path host postprocess parity');
|
|
1490
|
+
}
|
|
1491
|
+
if (options.siWarmAttempt) {
|
|
1492
|
+
const done = (async (): Promise<SelfImplementJobDone> => {
|
|
1493
|
+
let attempt: SiWarmChildAttempt;
|
|
1494
|
+
try {
|
|
1495
|
+
const acquired = await options.siWarmAttempt!(input);
|
|
1496
|
+
if (!acquired) {
|
|
1497
|
+
// No lease was fenced: use the existing one-shot Job path.
|
|
1498
|
+
const { siWarmAttempt: _unused, siWarmHostPostprocess: _postprocess, ...coldOptions } = options;
|
|
1499
|
+
return podSelfImplementSpawn(coldOptions)(input).done;
|
|
1500
|
+
}
|
|
1501
|
+
attempt = acquired;
|
|
1502
|
+
}
|
|
1503
|
+
catch (error) {
|
|
1504
|
+
// A lost exec reply may conceal an already opened PR. Never start another child here.
|
|
1505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1506
|
+
debug.log('pod.reuse', 'attempt-unavailable', { spaceId: input.spaceId, reason: message }, { level: 'warn' });
|
|
1507
|
+
return { exitCode: 1, output: '', error: { code: 'si-warm-reconcile-only', message } };
|
|
1508
|
+
}
|
|
1509
|
+
let transport: Awaited<ReturnType<typeof executeSiWarmTransport>>;
|
|
1510
|
+
try {
|
|
1511
|
+
transport = await executeSiWarmTransport({ command: attempt.command,
|
|
1512
|
+
pod: attempt.transport.pod, runner: attempt.transport.runner,
|
|
1513
|
+
leaseFenced: attempt.leaseFenced, payload: attempt.payload,
|
|
1514
|
+
credentialBundle: attempt.credentialBundle, now: attempt.transport.now });
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1517
|
+
try { await attempt.quarantineUnknown('transport-exception'); }
|
|
1518
|
+
catch (quarantineError) {
|
|
1519
|
+
// Both failures must survive: the first transport error names the cause, the
|
|
1520
|
+
// quarantine error names the unfinished disposition. Record the judgement.
|
|
1521
|
+
const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
|
|
1522
|
+
const combined = `transport:${message}; quarantine:${quarantineMessage}`;
|
|
1523
|
+
try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
|
|
1524
|
+
event: 'pod-reuse-quarantine-failed',
|
|
1525
|
+
data: { spaceId: input.spaceId, workerId: attempt.command?.workerId ?? null,
|
|
1526
|
+
leaseId: attempt.command?.leaseId ?? null, reason: combined } }); }
|
|
1527
|
+
catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
|
|
1528
|
+
event: 'pod-reuse-quarantine-failed', reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) }, { level: 'warn' }); }
|
|
1529
|
+
debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId, reason: combined }, { level: 'error' });
|
|
1530
|
+
return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown', message: combined } };
|
|
1531
|
+
}
|
|
1532
|
+
// The quarantine is confirmed: record the settled disposal (fail-soft — the
|
|
1533
|
+
// disposition survives a ledger write failure) before the reconcile-only return.
|
|
1534
|
+
try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
|
|
1535
|
+
event: 'pod-reuse-quarantined',
|
|
1536
|
+
data: { spaceId: input.spaceId, workerId: attempt.command?.workerId ?? null,
|
|
1537
|
+
leaseId: attempt.command?.leaseId ?? null, reason: 'transport-exception' } }); }
|
|
1538
|
+
catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
|
|
1539
|
+
event: 'pod-reuse-quarantined', reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) }, { level: 'warn' }); }
|
|
1540
|
+
return { exitCode: 1, output: '', error: { code: 'si-warm-reconcile-only', message } };
|
|
1541
|
+
}
|
|
1542
|
+
const validation = transport.boundary === 'exec-attempted'
|
|
1543
|
+
? assessSiWarmReply(attempt.command, transport.reply, transport.bytes,
|
|
1544
|
+
attempt.payload, attempt.credentialBundle)
|
|
1545
|
+
: undefined;
|
|
1546
|
+
const next = decideSiWarmNextStep({ boundary: transport.boundary,
|
|
1547
|
+
leaseFenced: attempt.leaseFenced, ...(validation ? { validation } : {}) });
|
|
1548
|
+
debug.log('pod.reuse', 'admission-decision', { spaceId: input.spaceId, boundary: transport.boundary,
|
|
1549
|
+
next: next.next, reason: transport.reason, validationReason: transport.validationReason ?? null });
|
|
1550
|
+
if (next.next === 'cold-job' && transport.boundary === 'not-entered' && attempt.leaseFenced) {
|
|
1551
|
+
const { siWarmAttempt: _unused, ...coldOptions } = options;
|
|
1552
|
+
// The acquired lease must not silently ride along into a cold Job: dispose it first,
|
|
1553
|
+
// record the disposition, and refuse to start the cold Job when disposal fails.
|
|
1554
|
+
const coldLease = attempt.command ? { workerId: attempt.command.workerId,
|
|
1555
|
+
podUid: attempt.command.podUid, leaseId: attempt.command.leaseId,
|
|
1556
|
+
generation: attempt.command.generation, key: attempt.command.key,
|
|
1557
|
+
sourceSha: attempt.command.sourceSha } : null;
|
|
1558
|
+
if (coldLease) {
|
|
1559
|
+
const recordColdDisposition = (event: string, action: string, residue: Record<string, unknown> = {}) => {
|
|
1560
|
+
debug.log('pod.reuse', event, { spaceId: input.spaceId, workerId: coldLease.workerId,
|
|
1561
|
+
leaseId: coldLease.leaseId, action, reason: transport.reason, ...residue });
|
|
1562
|
+
try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId, event,
|
|
1563
|
+
data: { spaceId: input.spaceId, workerId: coldLease.workerId, leaseId: coldLease.leaseId,
|
|
1564
|
+
action, reason: transport.reason, ...residue } }); }
|
|
1565
|
+
catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId, event,
|
|
1566
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
1567
|
+
};
|
|
1568
|
+
try {
|
|
1569
|
+
// The acquired lease is disposed by the same eligibility standard as the
|
|
1570
|
+
// entered path: process count zero, pinned-source git reset/clean/status,
|
|
1571
|
+
// credential count zero, and an observable return decision — before the
|
|
1572
|
+
// lease can go back into the warm pool or a cold Job starts.
|
|
1573
|
+
const returned = await cleanAndReturnChildPod(coldLease, { ...attempt.cleanup,
|
|
1574
|
+
observe: (event, data) => {
|
|
1575
|
+
debug.log('pod.reuse', event, { spaceId: input.spaceId, ...data },
|
|
1576
|
+
{ level: event === 'return-decision'
|
|
1577
|
+
&& ((data as { outcome?: string }).outcome === 'eligible'
|
|
1578
|
+
|| (data as { outcome?: string }).outcome === 'returned') ? 'info' : 'warn' });
|
|
1579
|
+
attempt.cleanup.observe(event, data);
|
|
1580
|
+
// A ledger write failure is an observation failure: the settled return
|
|
1581
|
+
// or quarantine disposition must survive it (fail-soft after the decision).
|
|
1582
|
+
try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
|
|
1583
|
+
event: `pod-reuse-${event}`,
|
|
1584
|
+
data: { spaceId: input.spaceId, ...data } }); }
|
|
1585
|
+
catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
|
|
1586
|
+
runId: attempt.command?.runId ?? input.spaceId, event: `pod-reuse-${event}`,
|
|
1587
|
+
reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) },
|
|
1588
|
+
{ level: 'warn' }); }
|
|
1589
|
+
} });
|
|
1590
|
+
recordColdDisposition('pod-reuse-cold-fallback-dispose', returned.outcome,
|
|
1591
|
+
{ dispositionReason: returned.reason });
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1594
|
+
try { await attempt.cleanup.quarantine(coldLease, { clean: false, reason: 'cold-fallback-dispose-failed', observedResidue: null }); }
|
|
1595
|
+
catch (quarantineError) {
|
|
1596
|
+
const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
|
|
1597
|
+
recordColdDisposition('pod-reuse-cold-fallback-dispose-failed', 'quarantine-failed',
|
|
1598
|
+
{ error: `disposal:${message}; quarantine:${quarantineMessage}` });
|
|
1599
|
+
return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown',
|
|
1600
|
+
message: `disposal:${message}; quarantine:${quarantineMessage}` } };
|
|
1601
|
+
}
|
|
1602
|
+
recordColdDisposition('pod-reuse-cold-fallback-dispose-failed', 'quarantined', { error: message });
|
|
1603
|
+
return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown', message } };
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return podSelfImplementSpawn(coldOptions)(input).done;
|
|
1607
|
+
}
|
|
1608
|
+
const command = attempt.command;
|
|
1609
|
+
const lease = command && { workerId: command.workerId, podUid: command.podUid,
|
|
1610
|
+
leaseId: command.leaseId, generation: command.generation, key: command.key,
|
|
1611
|
+
sourceSha: command.sourceSha };
|
|
1612
|
+
const recordQuarantine = (event: string, reason: string, residue: Record<string, unknown> = {}) => {
|
|
1613
|
+
debug.log('pod.reuse', event, { spaceId: input.spaceId, workerId: command?.workerId ?? null,
|
|
1614
|
+
leaseId: command?.leaseId ?? null, reason, ...residue }, { level: 'warn' });
|
|
1615
|
+
try { writeWarmLedger({ runId: command?.runId ?? input.spaceId, event,
|
|
1616
|
+
data: { spaceId: input.spaceId, workerId: command?.workerId ?? null,
|
|
1617
|
+
leaseId: command?.leaseId ?? null, reason, ...residue } }); }
|
|
1618
|
+
catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
|
|
1619
|
+
event, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
1620
|
+
};
|
|
1621
|
+
const retireLease = async (reason: string) => {
|
|
1622
|
+
recordQuarantine('pod-reuse-quarantine-intent', reason);
|
|
1623
|
+
if (!lease) {
|
|
1624
|
+
try { await attempt.quarantineUnknown(reason); }
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
// The unsettled quarantine is an account of its own: keep both the
|
|
1627
|
+
// primary path reason and the quarantine cause in the run ledger,
|
|
1628
|
+
// then let the caller surface the unknown worker state.
|
|
1629
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1630
|
+
recordQuarantine('pod-reuse-quarantine-failed', `${reason}; quarantine:${message}`);
|
|
1631
|
+
throw error;
|
|
1632
|
+
}
|
|
1633
|
+
recordQuarantine('pod-reuse-quarantined', reason);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
// An uncertain exec may still be running. Keep it fenced and revoke its run credentials.
|
|
1637
|
+
let processRemaining: number | null = null;
|
|
1638
|
+
let credentialRemaining: number | null = null;
|
|
1639
|
+
try { processRemaining = (await attempt.cleanup.stopChildProcesses(lease)).remaining; }
|
|
1640
|
+
catch (error) { debug.log('pod.reuse', 'process-stop-failed', { spaceId: input.spaceId,
|
|
1641
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
1642
|
+
try { credentialRemaining = (await attempt.cleanup.removeRunCredentials(lease)).remaining; }
|
|
1643
|
+
catch (error) { debug.log('pod.reuse', 'credential-revoke-failed', { spaceId: input.spaceId,
|
|
1644
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
1645
|
+
const observedResidue = [processRemaining, credentialRemaining]
|
|
1646
|
+
.filter((value): value is number => typeof value === 'number'
|
|
1647
|
+
&& Number.isSafeInteger(value) && value >= 0)
|
|
1648
|
+
.reduce((sum, value) => sum + value, 0);
|
|
1649
|
+
try {
|
|
1650
|
+
await attempt.cleanup.quarantine(lease, { clean: false, reason,
|
|
1651
|
+
observedResidue: observedResidue > 0 ? observedResidue : null });
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
// The lease stays unfenced and the worker state unknown: record the
|
|
1654
|
+
// unsettled disposal (primary reason plus the quarantine cause)
|
|
1655
|
+
// before the caller reports the unknown worker state.
|
|
1656
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1657
|
+
recordQuarantine('pod-reuse-quarantine-failed', `${reason}; quarantine:${message}`);
|
|
1658
|
+
throw error;
|
|
1659
|
+
}
|
|
1660
|
+
recordQuarantine('pod-reuse-quarantined', reason, { processRemaining, credentialRemaining });
|
|
1661
|
+
};
|
|
1662
|
+
if (next.next !== 'adopt-warm' || !transport.measured || !command
|
|
1663
|
+
|| !transport.reply || !transport.bytes) {
|
|
1664
|
+
const reconcileDisposition = transport.verifiedBytesForReconcile
|
|
1665
|
+
? parseSelfImplementJson(new TextDecoder().decode(transport.verifiedBytesForReconcile.terminal))
|
|
1666
|
+
: null;
|
|
1667
|
+
const hostDisposition = reconcileDisposition ? (({ worktreePath: _podOnly, ...host }) => host)(reconcileDisposition) : null;
|
|
1668
|
+
let quarantineError: string | undefined;
|
|
1669
|
+
try { await retireLease(validation?.reason ?? 'response-unknown'); }
|
|
1670
|
+
catch (error) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
|
|
1671
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'error' });
|
|
1672
|
+
quarantineError = error instanceof Error ? error.message : String(error); }
|
|
1673
|
+
return { exitCode: 1, output: '',
|
|
1674
|
+
...(hostDisposition ? { disposition: hostDisposition } : {}),
|
|
1675
|
+
error: { code: quarantineError
|
|
1676
|
+
|| (transport.quarantineWorker && !transport.quarantineRecorded)
|
|
1677
|
+
? 'si-warm-worker-state-unknown' : 'si-warm-reconcile-only',
|
|
1678
|
+
message: `si-v1 exec may have run; reconcile before retry (${transport.reason})${transport.quarantineError ? `; transport quarantine: ${transport.quarantineError}` : ''}${quarantineError ? `; worker-state-unknown: ${quarantineError}` : ''}` } };
|
|
1679
|
+
}
|
|
1680
|
+
const terminal = new TextDecoder().decode(transport.bytes.terminal);
|
|
1681
|
+
const disposition = parseSelfImplementJson(terminal);
|
|
1682
|
+
if (!disposition) {
|
|
1683
|
+
let quarantineError: string | undefined;
|
|
1684
|
+
try { await retireLease('terminal-disposition-unreadable'); }
|
|
1685
|
+
catch (error) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
|
|
1686
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'error' });
|
|
1687
|
+
quarantineError = error instanceof Error ? error.message : String(error); }
|
|
1688
|
+
return { exitCode: 1, output: '', error: { code: quarantineError
|
|
1689
|
+
? 'si-warm-worker-state-unknown' : 'si-warm-terminal-unreadable',
|
|
1690
|
+
message: quarantineError ?? 'validated si-v1 terminal was not a self-implement disposition' } };
|
|
1691
|
+
}
|
|
1692
|
+
try { await attempt.adopt(command, transport.reply, transport.bytes); }
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1695
|
+
let quarantineError: string | undefined;
|
|
1696
|
+
try { await retireLease('adopt-failed'); }
|
|
1697
|
+
catch (quarantineFailure) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
|
|
1698
|
+
reason: quarantineFailure instanceof Error ? quarantineFailure.message : String(quarantineFailure) }, { level: 'error' });
|
|
1699
|
+
quarantineError = quarantineFailure instanceof Error ? quarantineFailure.message : String(quarantineFailure); }
|
|
1700
|
+
debug.log('pod.reuse', 'adopt-failed', { spaceId: input.spaceId, reason: message }, { level: 'warn' });
|
|
1701
|
+
return { exitCode: 1, output: '', disposition, error: { code: quarantineError
|
|
1702
|
+
? 'si-warm-worker-state-unknown' : 'si-warm-reconcile-only', message: quarantineError ?? message } };
|
|
1703
|
+
}
|
|
1704
|
+
let returnOutcome: 'returned' | 'quarantined' = 'quarantined';
|
|
1705
|
+
let workerStateUnknown: string | undefined;
|
|
1706
|
+
try {
|
|
1707
|
+
const returned = await cleanAndReturnChildPod(lease!, { ...attempt.cleanup,
|
|
1708
|
+
observe: (event, data) => {
|
|
1709
|
+
debug.log('pod.reuse', event, { spaceId: input.spaceId, ...data },
|
|
1710
|
+
{ level: event === 'return-decision' && (data.outcome === 'eligible' || data.outcome === 'returned') ? 'info' : 'warn' });
|
|
1711
|
+
attempt.cleanup.observe(event, data);
|
|
1712
|
+
// The lease disposition is already settled by the time this callback runs; the warm
|
|
1713
|
+
// ledger is an observation of it. A ledger write failure must stay an observation
|
|
1714
|
+
// failure — escaping here would quarantine a lease that was already returned.
|
|
1715
|
+
try { writeWarmLedger({ runId: command.runId, event: `pod-reuse-${event}`,
|
|
1716
|
+
data: { spaceId: input.spaceId, ...data } }); }
|
|
1717
|
+
catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId, runId: command.runId,
|
|
1718
|
+
event: `pod-reuse-${event}`, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
1719
|
+
} });
|
|
1720
|
+
returnOutcome = returned.outcome;
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
workerStateUnknown = error instanceof Error ? error.message : String(error);
|
|
1723
|
+
debug.log('pod.reuse', 'return-unavailable', { spaceId: input.spaceId,
|
|
1724
|
+
reason: workerStateUnknown }, { level: 'error' });
|
|
1725
|
+
// The inner return/quarantine result is unclear: quarantine the lease explicitly,
|
|
1726
|
+
// and only a second quarantine failure leaves the worker state unknown.
|
|
1727
|
+
try {
|
|
1728
|
+
await attempt.quarantineUnknown(`return-quarantine-unavailable:${workerStateUnknown}`);
|
|
1729
|
+
try { writeWarmLedger({ runId: command!.runId, event: 'pod-reuse-quarantined',
|
|
1730
|
+
data: { spaceId: input.spaceId, workerId: command!.workerId, leaseId: command!.leaseId,
|
|
1731
|
+
reason: `return-quarantine-unavailable:${workerStateUnknown}` } }); }
|
|
1732
|
+
catch { /* ledger loss must not mask the quarantine outcome */ }
|
|
1733
|
+
// The explicit quarantine settled the disposal: the caller result must
|
|
1734
|
+
// account for it as quarantined instead of claiming an unknown state.
|
|
1735
|
+
workerStateUnknown = undefined;
|
|
1736
|
+
} catch (quarantineError) {
|
|
1737
|
+
const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
|
|
1738
|
+
workerStateUnknown = `return:${workerStateUnknown}; quarantine:${quarantineMessage}`;
|
|
1739
|
+
try { writeWarmLedger({ runId: command!.runId, event: 'pod-reuse-quarantine-failed',
|
|
1740
|
+
data: { spaceId: input.spaceId, workerId: command!.workerId, leaseId: command!.leaseId,
|
|
1741
|
+
reason: workerStateUnknown } }); }
|
|
1742
|
+
catch { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
|
|
1743
|
+
event: 'pod-reuse-quarantine-failed', reason: workerStateUnknown }, { level: 'warn' }); }
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
const log = new TextDecoder().decode(transport.bytes.log);
|
|
1747
|
+
// Pod checkout paths cannot be used by the host supervisor.
|
|
1748
|
+
const { worktreePath: _podOnly, ...hostDisposition } = disposition;
|
|
1749
|
+
if (workerStateUnknown) return { exitCode: 1, output: log.slice(-4096), disposition: hostDisposition,
|
|
1750
|
+
error: { code: 'si-warm-worker-state-unknown', message: workerStateUnknown } };
|
|
1751
|
+
debug.log('pod.reuse', 'child-result', { spaceId: input.spaceId, workerId: command.workerId,
|
|
1752
|
+
leaseId: command.leaseId, returnOutcome, stage: disposition.stage ?? null });
|
|
1753
|
+
try {
|
|
1754
|
+
return await options.siWarmHostPostprocess!({ exitCode: transport.reply.exitCode,
|
|
1755
|
+
output: log.slice(-4096), disposition: hostDisposition }, attempt, input);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1758
|
+
debug.log('pod.reuse', 'host-postprocess-failed', { spaceId: input.spaceId,
|
|
1759
|
+
reason: message }, { level: 'error' });
|
|
1760
|
+
return { exitCode: 1, output: log.slice(-4096), disposition: hostDisposition,
|
|
1761
|
+
error: { code: 'si-warm-host-postprocess-failed', message } };
|
|
1762
|
+
}
|
|
1763
|
+
})();
|
|
1764
|
+
return { address: `self-impl:${input.spaceId}`, done };
|
|
1765
|
+
}
|
|
1313
1766
|
const name = podJobName(input.spaceId);
|
|
1314
1767
|
let parentGoal: string | undefined;
|
|
1315
1768
|
let parentGoalUnreadable = false;
|
|
@@ -1599,12 +2052,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1599
2052
|
const jobArgs = zaiRoute === 'openrouter' ? zaiOverflowChildArgs(args, ZAI_OPENROUTER_FALLBACK_MODEL) : args;
|
|
1600
2053
|
const grok = options.provider === 'grok';
|
|
1601
2054
|
const openrouter = options.provider === 'openrouter' || zaiRoute === 'openrouter';
|
|
2055
|
+
const openGateway = options.provider === 'opengateway';
|
|
1602
2056
|
const anthropic = options.provider === 'anthropic';
|
|
1603
2057
|
const bedrockChild = options.provider === 'bedrock';
|
|
1604
2058
|
const zaiChild = zaiRoute === 'zai';
|
|
1605
2059
|
/** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` · bedrock·glm 은 이름 있는 Secret 으로만). */
|
|
1606
|
-
const apiKeyChild = openrouter || anthropic || bedrockChild || zaiChild;
|
|
1607
|
-
const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
|
|
2060
|
+
const apiKeyChild = openrouter || openGateway || anthropic || bedrockChild || zaiChild;
|
|
2061
|
+
const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : openGateway ? 'opengateway' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
|
|
1608
2062
|
debug.log('self-implement.pod', 'account', { spaceId: input.spaceId, account: plannedAccount, brokered: Boolean(options.accountBroker) });
|
|
1609
2063
|
const plannedCodexAccounts = grok || apiKeyChild || !options.accountBroker || !options.rotationAccounts ? undefined : [plannedAccount, ...options.rotationAccounts.filter((candidate) => candidate !== plannedAccount && candidate !== failedAccount)];
|
|
1610
2064
|
if (plannedCodexAccounts && !options.rotationAccounts!.includes(plannedAccount)) throw new Error(`pod: 배분 계정 ${plannedAccount} 이 회전 계획에 없다`);
|
|
@@ -1647,6 +2101,17 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1647
2101
|
}) : undefined;
|
|
1648
2102
|
const openrouterKey = openrouter ? env.OPENROUTER_API_KEY?.trim() || (options.readKeyCache ?? defaultReadKeyCache)('OPENROUTER_API_KEY')?.trim() : undefined;
|
|
1649
2103
|
if (openrouter && !openrouterKey) throw new Error('pod: Pod 에 openrouter 키 없음 — 호스트 키 캐시 또는 OPENROUTER_API_KEY 필요');
|
|
2104
|
+
const openGatewayEnvKey = openGateway ? env.OPENGATEWAY_API_KEY?.trim() : undefined;
|
|
2105
|
+
const openGatewayCachedKey = openGateway ? (options.readKeyCache ?? defaultReadKeyCache)('OPENGATEWAY_API_KEY')?.trim() : undefined;
|
|
2106
|
+
if (openGatewayEnvKey && openGatewayCachedKey && openGatewayEnvKey !== openGatewayCachedKey)
|
|
2107
|
+
throw new Error('pod: opengateway env와 키 캐시가 충돌 — Pod 발사 거부');
|
|
2108
|
+
const openGatewayHostConfig = openGateway ? (options.openGatewayConfig ?? getUserConfig)() : undefined;
|
|
2109
|
+
const openGatewayConfiguredKeys = openGatewayHostConfig ? configuredOpenGatewayKeys(openGatewayHostConfig.llm) : [];
|
|
2110
|
+
if (openGatewayConfiguredKeys.length > 1 || openGatewayConfiguredKeys.some((key) =>
|
|
2111
|
+
(openGatewayEnvKey && openGatewayEnvKey !== key) || (openGatewayCachedKey && openGatewayCachedKey !== key)))
|
|
2112
|
+
throw new Error('pod: opengateway 호스트 설정과 env/키 캐시가 충돌 — Pod 발사 거부');
|
|
2113
|
+
const openGatewayKey = openGatewayEnvKey || openGatewayCachedKey || openGatewayConfiguredKeys[0];
|
|
2114
|
+
if (openGateway && !openGatewayKey) throw new Error('pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요');
|
|
1650
2115
|
// POD-ANTHROPIC-PROVIDER — 제품 자격 해석(`getAnthropicApiKey` · 키 캐시 → env)으로 읽는다. 못 읽으면 Pod 를 안 띄운다(fail-closed).
|
|
1651
2116
|
// ⛔ 값은 로그·오류 문면에 안 싣는다 — «있었다» 만.
|
|
1652
2117
|
const anthropicKey = anthropic
|
|
@@ -1717,7 +2182,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1717
2182
|
}
|
|
1718
2183
|
}
|
|
1719
2184
|
// Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
|
|
1720
|
-
const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
|
|
2185
|
+
const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(openGateway ? ['OPENGATEWAY_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
|
|
1721
2186
|
&& key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
|
|
1722
2187
|
&& (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
|
|
1723
2188
|
const skillEnvs: Record<string, string> = options.skillEnv
|
|
@@ -1863,7 +2328,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1863
2328
|
authorChoice = { ...authorChoice, model: childModel, modelSource: 'fallback-child' };
|
|
1864
2329
|
} else debug.log('self-implement.pod', 'role-llm-armed', { job: name, role: 'planning', model: authorChoice.model, source: authorChoice.modelSource, account: roleCodexAccount });
|
|
1865
2330
|
}
|
|
1866
|
-
const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
|
|
2331
|
+
const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'OPENGATEWAY_API_KEY' && openGateway ? openGatewayKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
|
|
1867
2332
|
const hostFallbackNames = (options.fallbackConfig ?? getUserConfig)().llm.fallbackChain;
|
|
1868
2333
|
const normalizedFallback = normalizeFallbackChain(hostFallbackNames);
|
|
1869
2334
|
let fallbackSteps = normalizedFallback.chain;
|
|
@@ -1932,6 +2397,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1932
2397
|
// 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
|
|
1933
2398
|
// ☸️ 노드 레지스트리에 이 판이 있으면 그것을 pull 한다(델타 · 대표 2026-09-26) — 라벨 판정은 로컬 이미지 이름으로 한다.
|
|
1934
2399
|
const jobImage = member?.imageRef ?? image;
|
|
2400
|
+
// A pool member may pull a different image than the host's local one.
|
|
2401
|
+
// The local Docker label cannot prove that member image's commit.
|
|
2402
|
+
const localImageCommit = options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit;
|
|
2403
|
+
const jobImageCommit = jobImage === image ? localImageCommit : null;
|
|
1935
2404
|
const launchRunId = retryAccount || oomRetried || ghLoginRetried ? mintRunId() : childRunId;
|
|
1936
2405
|
let liveChildRunId = launchRunId;
|
|
1937
2406
|
// TA-POD-WARMBOOT-PARITY — the id the followed Job prints in its result marker (null = wait on the condition only).
|
|
@@ -2167,7 +2636,22 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2167
2636
|
debug.log('self-implement.pod', fit.fitted ? 'grok-deadline-fitted' : 'grok-deadline-kept', { job: name, requestedSeconds: jobDeadlineSeconds, deadlineSeconds: fit.deadlineSeconds, availableSeconds: fit.availableSeconds, relay: Boolean(credentialRelay), marginMs: POD_GROK_EXPIRY_MARGIN_MS });
|
|
2168
2637
|
jobDeadlineSeconds = fit.deadlineSeconds;
|
|
2169
2638
|
}
|
|
2170
|
-
|
|
2639
|
+
// POD-LAUNCH-PROVENANCE — fail-closed: a si-task Job without its ★ identity is never applied.
|
|
2640
|
+
let provenance: PodProvenanceMetadata;
|
|
2641
|
+
try {
|
|
2642
|
+
let version: string | undefined;
|
|
2643
|
+
try { version = packageVersion(); } catch { version = undefined; }
|
|
2644
|
+
provenance = podProvenanceMetadata(siTaskPodProvenance({ env, runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), workIdentity,
|
|
2645
|
+
requestedAt: new Date((options.now ?? Date.now)()).toISOString(), feature: input.feature, launchHost: resolveHostId(env),
|
|
2646
|
+
commit: seedSource.kind === 'commit' ? seedSource.sha : seedCommit ?? jobImageCommit, ...(version ? { version } : {}),
|
|
2647
|
+
...(memoryRequest ? { memReq: memoryRequest } : {}) }));
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2650
|
+
debug.log('pod.provenance', 'si-task-refused', { job: name, reason: message }, { level: 'warn' });
|
|
2651
|
+
cleanupSecret();
|
|
2652
|
+
return { exitCode: 1, output: '', error: { code: 'pod-provenance', message } };
|
|
2653
|
+
}
|
|
2654
|
+
const job = podJobManifest({ provenance, name, namespace, fallbackChain, ...(resultMarkerLine ? { resultMarkerLine } : {}), image: jobImage, workIdentity, ...(env.ELANOUS_TASK_BOUND_PR && /^[1-9]\d*$/.test(env.ELANOUS_TASK_BOUND_PR) && Number.isSafeInteger(Number(env.ELANOUS_TASK_BOUND_PR)) ? { boundPrNumber: Number(env.ELANOUS_TASK_BOUND_PR) } : {}), ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: jobDeadlineSeconds, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: jobImageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(roleCodexCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}), ...(zaiFallbackSpec ? { zaiFallback: zaiFallbackSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
2171
2655
|
const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
|
|
2172
2656
|
const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
|
|
2173
2657
|
jobApplyStartedAt = Date.now();
|
|
@@ -2175,6 +2659,15 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2175
2659
|
zaiPendingApply.delete(name); // apply 뒤엔 Job 라벨이 센다(성공) · 실패면 셀 것이 없다
|
|
2176
2660
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
2177
2661
|
releaseAdmission?.applied?.(name, context, namespace);
|
|
2662
|
+
const imageFacts = { job: name, childRunId: liveChildRunId, image: jobImage, imageCommit: jobImageCommit, context };
|
|
2663
|
+
// POD-LAUNCH-PROVENANCE — append-only «started» row: who · why · when, on the parent's existing run ledger.
|
|
2664
|
+
const startedRow = { job: name, run: launchRunId, attempt: oomRetried ? 2 : 1, provenance: provenanceRowFromAnnotations(provenance.annotations) };
|
|
2665
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: POD_LAUNCH_STARTED_EVENT, timestamp: new Date((options.now ?? Date.now)()).toISOString(), data: startedRow }, ledgerDir); }
|
|
2666
|
+
catch (error) { debug.log('pod.provenance', 'history-unavailable', { job: name, row: 'started', reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
2667
|
+
debug.log('pod.provenance', 'job-started', startedRow);
|
|
2668
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-job-image', data: imageFacts }, ledgerDir); }
|
|
2669
|
+
catch (error) { debug.log('self-implement.pod', 'job-image-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
2670
|
+
debug.log('self-implement.pod', 'job-image', imageFacts);
|
|
2178
2671
|
// The injected observer does not acquire a production lease. The gate consumer
|
|
2179
2672
|
// must compare the Job result and explicitly finalize before enabling replay.
|
|
2180
2673
|
if (!oomRetried && input.warmWorkerCommand && options.warmPodRequest) {
|
|
@@ -2241,11 +2734,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2241
2734
|
}
|
|
2242
2735
|
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
|
|
2243
2736
|
recorded = true;
|
|
2244
|
-
debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, ...(openrouter ? { openrouterKeyPresent: passEnv.includes('OPENROUTER_API_KEY') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), ...(bedrockSpec ? { bedrockSecret: bedrockSpec.secretName, bedrockSecretKeys: bedrockSpec.keys, bedrockRegionFrom: bedrockSpec.region.from } : {}), ...(zaiRoute ? { zaiRoute, ...(zaiSpec ? { zaiSecret: zaiSpec.secretName } : {}) } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
|
|
2737
|
+
debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, ...(openrouter ? { openrouterKeyPresent: passEnv.includes('OPENROUTER_API_KEY') } : {}), ...(openGateway ? { openGatewayKeyPresent: passEnv.includes('OPENGATEWAY_API_KEY') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), ...(bedrockSpec ? { bedrockSecret: bedrockSpec.secretName, bedrockSecretKeys: bedrockSpec.keys, bedrockRegionFrom: bedrockSpec.region.from } : {}), ...(zaiRoute ? { zaiRoute, ...(zaiSpec ? { zaiSecret: zaiSpec.secretName } : {}) } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
|
|
2245
2738
|
let state: 'complete' | 'failed' | 'aborted' = 'failed';
|
|
2246
2739
|
let failedReason = '';
|
|
2247
2740
|
let containerReason: string | null = null;
|
|
2248
2741
|
let containerExitCode: number | null = null;
|
|
2742
|
+
// JOB-FAILURE-TERMINATION-CAPTURE — this Job's Kubernetes facts, read once at the failure verdict (before GC).
|
|
2743
|
+
// Observation only: failedReason · containerReason · containerExitCode keep driving the classification.
|
|
2744
|
+
let podTermination: PodTermination | null = null;
|
|
2249
2745
|
// METER-POD-ROLLUP-GAP — abort 때 Job 삭제 «전»에 거둔 로그 사본(삭제 뒤엔 PARTIAL 줄도 못 읽는다).
|
|
2250
2746
|
let abortLogs = '';
|
|
2251
2747
|
// ⭐ 런 «도중» 원장 증분 회수 — 호스트 슈퍼바이저가 Pod 걸음을 실시간으로 본다(🅣 요청 · 힐 루프 runtime 성형의 전제).
|
|
@@ -2253,11 +2749,34 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2253
2749
|
// ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
|
|
2254
2750
|
let livePartialLine: string | null = null;
|
|
2255
2751
|
let pollsSinceCapture = 0;
|
|
2752
|
+
let imageSnapshotRecorded = false;
|
|
2753
|
+
let terminalImageRecorded = false;
|
|
2754
|
+
const recordPodImage = (final: boolean): void => {
|
|
2755
|
+
if (final ? terminalImageRecorded : imageSnapshotRecorded) return;
|
|
2756
|
+
const observed = readJobPodImage(kubectl, namespace, name, lifecycleJobUid);
|
|
2757
|
+
if (!final && !observed.podName) return;
|
|
2758
|
+
const append = (isFinal: boolean): void => {
|
|
2759
|
+
const data = { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1,
|
|
2760
|
+
image: jobImage, imageCommit: jobImageCommit, context, ...observed, final: isFinal };
|
|
2761
|
+
try {
|
|
2762
|
+
appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-image-digest', data }, ledgerDir);
|
|
2763
|
+
if (isFinal) terminalImageRecorded = true;
|
|
2764
|
+
else imageSnapshotRecorded = true;
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
debug.log('self-implement.pod', 'pod-image-digest-ledger-unavailable', {
|
|
2767
|
+
job: name, final: isFinal, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
2768
|
+
}
|
|
2769
|
+
debug.log('self-implement.pod', 'pod-image-digest', data);
|
|
2770
|
+
};
|
|
2771
|
+
if (final && !imageSnapshotRecorded && observed.podName) append(false);
|
|
2772
|
+
append(final);
|
|
2773
|
+
};
|
|
2256
2774
|
const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
|
|
2257
2775
|
const markerWait = markerToken ? await import('./pod-result-marker-wait.js') : null;
|
|
2258
2776
|
const waitStartedAt = Date.now();
|
|
2259
2777
|
for (;;) {
|
|
2260
2778
|
if (input.signal?.aborted) {
|
|
2779
|
+
recordPodImage(true);
|
|
2261
2780
|
// METER-POD-ROLLUP-GAP — Job 삭제 «전»에 로그를 한 번 거둔다: PARTIAL 줄이 삭제와 함께 사라지면 사용량이 0 처럼 보인다.
|
|
2262
2781
|
try {
|
|
2263
2782
|
const captured = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']);
|
|
@@ -2267,16 +2786,24 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2267
2786
|
state = 'aborted';
|
|
2268
2787
|
break;
|
|
2269
2788
|
}
|
|
2789
|
+
if (!imageSnapshotRecorded) recordPodImage(false);
|
|
2270
2790
|
const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
|
|
2271
2791
|
const types = g.stdout;
|
|
2272
2792
|
if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
|
|
2273
2793
|
if (/Failed|FailureTarget/.test(types)) {
|
|
2274
2794
|
releaseAdmission?.observed?.();
|
|
2275
2795
|
state = 'failed';
|
|
2276
|
-
|
|
2277
|
-
|
|
2796
|
+
// One Job read serves both: column one is the classification reason, all three the capture (no re-read a GC could empty).
|
|
2797
|
+
let jobRead: { status: number | null; stdout: string } | null = null;
|
|
2798
|
+
let jobReadError: unknown;
|
|
2799
|
+
try { jobRead = kubectl(['-n', namespace, 'get', 'job', name, '-o', `jsonpath=${POD_TERMINATION_JOB_JSONPATH}`]); }
|
|
2800
|
+
catch (error) { jobReadError = error; }
|
|
2801
|
+
failedReason = jobRead?.status === 0 ? (jobRead.stdout.split('\t')[0] ?? '').trim() : '';
|
|
2802
|
+
// One Pods read serves both: its first three columns are the classification layout, the rest the termination capture.
|
|
2803
|
+
let podsRead: { status: number | null; stdout: string } | null = null;
|
|
2278
2804
|
try {
|
|
2279
|
-
const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o',
|
|
2805
|
+
const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', `jsonpath=${POD_TERMINATION_PODS_JSONPATH}`]);
|
|
2806
|
+
podsRead = pods;
|
|
2280
2807
|
if (pods.status === 0 && pods.stdout) {
|
|
2281
2808
|
const rows = pods.stdout.trimEnd().split('\n').map((line) => line.split('\t'));
|
|
2282
2809
|
const latest = rows.filter(([created]) => created && !Number.isNaN(Date.parse(created)))
|
|
@@ -2289,6 +2816,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2289
2816
|
}
|
|
2290
2817
|
} catch { /* Pod 가 사라졌거나 조회 불가 — 종료 사유는 미상. */ }
|
|
2291
2818
|
debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
|
|
2819
|
+
podTermination = readPodTermination({ kubectl, namespace, job: name, path: 'job-failed-condition', jobRead, podsRead, jobUid: lifecycleJobUid });
|
|
2820
|
+
if (jobReadError !== undefined) {
|
|
2821
|
+
// Today's outcome stays (the throw ends the spawn as pod-error); the capture is in the ledger first.
|
|
2822
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence: null, childRunId: liveChildRunId, job: name, podTermination } }, ledgerDir); }
|
|
2823
|
+
catch (error) { debug.log('self-implement.pod', 'child-evidence-write-failed', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
2824
|
+
debug.log('self-implement.pod', 'pod-termination-captured', { job: name, podTermination, jobReadThrew: true }, { level: 'warn' });
|
|
2825
|
+
throw jobReadError;
|
|
2826
|
+
}
|
|
2292
2827
|
break;
|
|
2293
2828
|
}
|
|
2294
2829
|
// TA-POD-WARMBOOT-PARITY: the condition is not there yet — the child may already be done (k3d condition lag).
|
|
@@ -2300,6 +2835,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2300
2835
|
state = seen === 0 ? 'complete' : 'failed';
|
|
2301
2836
|
// The child's own rc (the Pod's terminated state may lag like the condition) — exit-code paths read it as before.
|
|
2302
2837
|
if (seen !== 0) containerExitCode = seen;
|
|
2838
|
+
if (seen !== 0) podTermination = readPodTermination({ kubectl, namespace, job: name, path: 'result-marker', jobUid: lifecycleJobUid });
|
|
2303
2839
|
debug.log('self-implement.pod', 'result-marker-early', { job: name, waitedMs: Date.now() - waitStartedAt, rc: seen });
|
|
2304
2840
|
break;
|
|
2305
2841
|
}
|
|
@@ -2342,6 +2878,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2342
2878
|
}
|
|
2343
2879
|
await sleep(options.pollMs ?? 15_000);
|
|
2344
2880
|
}
|
|
2881
|
+
if (!terminalImageRecorded) recordPodImage(true);
|
|
2345
2882
|
let logs = '';
|
|
2346
2883
|
let logTailReason: string | undefined;
|
|
2347
2884
|
try {
|
|
@@ -2521,23 +3058,72 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2521
3058
|
}
|
|
2522
3059
|
const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
|
|
2523
3060
|
? lastPodChildFailure(logs) : null;
|
|
3061
|
+
let childPrFailure: ReturnType<typeof podPrOpenFailure> = null;
|
|
3062
|
+
if (childFailure) {
|
|
3063
|
+
try {
|
|
3064
|
+
const entries = loadRunLedger(liveChildRunId, ledgerDir);
|
|
3065
|
+
const entry = entries?.slice().reverse().find((row) => row.event === 'pr-open-failed');
|
|
3066
|
+
childPrFailure = podPrOpenFailure(entry?.data, childFailure.rawError);
|
|
3067
|
+
} catch { /* child ledger is optional; terminal result remains available */ }
|
|
3068
|
+
childPrFailure ??= podPrOpenFailure(null, childFailure.rawError);
|
|
3069
|
+
}
|
|
3070
|
+
if (childPrFailure && disposition) {
|
|
3071
|
+
disposition.childEvidence = {
|
|
3072
|
+
...(disposition.childEvidence ?? {}),
|
|
3073
|
+
prFailure: {
|
|
3074
|
+
stage: childPrFailure.prFailureStage,
|
|
3075
|
+
source: childPrFailure.prFailureSource,
|
|
3076
|
+
errorTail: childPrFailure.childErrorTail,
|
|
3077
|
+
...(childPrFailure.blockingPrNumber ? { blockingPrNumber: childPrFailure.blockingPrNumber } : {}),
|
|
3078
|
+
...(childPrFailure.candidateCardId ? { candidateCardId: childPrFailure.candidateCardId } : {}),
|
|
3079
|
+
...(childPrFailure.blockingCardId ? { blockingCardId: childPrFailure.blockingCardId } : {}),
|
|
3080
|
+
},
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
2524
3083
|
let noResultDiagnostic: string | null = null;
|
|
2525
|
-
// POD-NORESULT:
|
|
2526
|
-
|
|
3084
|
+
// POD-NORESULT: a terminal Job without a parsed disposition is incomplete even when the Job condition says Complete.
|
|
3085
|
+
const noResultCandidate = !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' &&
|
|
3086
|
+
(state === 'complete' || (state === 'failed' && containerExitCode !== 0));
|
|
3087
|
+
if (noResultCandidate) {
|
|
2527
3088
|
try { noResultDiagnostic = podNoResultDiagnostic(readFileSync(runLedgerPath(liveChildRunId, ledgerDir), 'utf8')); }
|
|
2528
3089
|
catch { /* ledger not returned: do not invent a stage */ }
|
|
2529
3090
|
noResultDiagnostic ??= 'child terminal result missing; last ledger stage=unknown; round=unknown; mustFix=unknown';
|
|
2530
3091
|
}
|
|
2531
|
-
|
|
3092
|
+
// POD-NORESULT-LOG-TAIL: the Job runs with backoffLimit 0, so the failed Pod — and its container log —
|
|
3093
|
+
// is collected right after this. When the child ledger cannot even name the stage, read the tail here,
|
|
3094
|
+
// before deletion, so the parent keeps a maskable reason instead of `childEvidence=null`.
|
|
3095
|
+
let noResultLogEvidence: Record<string, unknown> | null = null;
|
|
3096
|
+
if (noResultDiagnostic?.includes('stage=unknown')) {
|
|
3097
|
+
const logRead = readPodNoResultLogTail({ run: kubectl, namespace, job: name, maxLines: POD_NO_RESULT_LOG_TAIL_LINES });
|
|
3098
|
+
if ('tail' in logRead) {
|
|
3099
|
+
noResultLogEvidence = { stderr: logRead.tail.join('\n'), logTail: logRead.tail, logTailLines: logRead.tail.length };
|
|
3100
|
+
debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, lines: logRead.tail.length, logTail: logRead.tail }, { compact: { stringMax: 500 } });
|
|
3101
|
+
} else {
|
|
3102
|
+
noResultLogEvidence = { stderr: `log-unavailable: ${logRead.unavailable}` };
|
|
3103
|
+
debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, logUnavailable: logRead.unavailable }, { level: 'warn' });
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error, ...(childPrFailure ?? {}) }, { compact: { stringMax: 500 } });
|
|
2532
3107
|
if (noResultDiagnostic && !noResultDiagnostic.includes('stage=unknown')) {
|
|
2533
3108
|
debug.log('self-implement.pod', 'no-result-ledger-diagnostic', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic }, { compact: { stringMax: 500 } });
|
|
2534
3109
|
}
|
|
3110
|
+
// POD-LAUNCH-PROVENANCE — append-only «finished» row paired with its started row on job·run.
|
|
3111
|
+
// Keyed on launchRunId like the started row; a reattached Job's inherited child run rides along as childRun.
|
|
3112
|
+
// exitCode stays null when the container's code was not read (never filled in as 0).
|
|
3113
|
+
const finishedRow = { job: name, run: launchRunId, ...(liveChildRunId !== launchRunId ? { childRun: liveChildRunId } : {}),
|
|
3114
|
+
attempt: oomRetried ? 2 : 1, phase: state, exitCode: containerExitCode,
|
|
3115
|
+
reason: failedReason || containerReason || (state === 'complete' ? 'complete' : 'unknown') };
|
|
3116
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: POD_LAUNCH_FINISHED_EVENT, timestamp: new Date((options.now ?? Date.now)()).toISOString(), data: finishedRow }, ledgerDir); }
|
|
3117
|
+
catch (error) { debug.log('pod.provenance', 'history-unavailable', { job: name, row: 'finished', reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
3118
|
+
debug.log('pod.provenance', 'job-finished', finishedRow);
|
|
2535
3119
|
debug.log('self-implement.pod', 'job-finished', { job: name, ...(member ? { context: member.context } : {}), state, containerReason, stage: disposition?.stage ?? null, prUrl: disposition?.prUrl ?? null, childRunId: liveChildRunId, ledgerCompleteness,
|
|
3120
|
+
...(podTermination ? { podTermination } : {}),
|
|
2536
3121
|
...(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
|
|
2537
3122
|
? { ...(childFailure ? { childStage: childFailure.stage } : disposition?.stage ? { childStage: disposition.stage } : {}),
|
|
2538
3123
|
// POD-NORESULT: a parsed result row without error text is not a missing result line (UX 10-07 01:01: 28 of 28 exit-2 Jobs printed one).
|
|
2539
|
-
childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line')
|
|
2540
|
-
|
|
3124
|
+
childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line'),
|
|
3125
|
+
...(childPrFailure ?? {}) } : {}) },
|
|
3126
|
+
(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded') ? { compact: { stringMax: 500 } } : undefined);
|
|
2541
3127
|
// POD-LIFECYCLE-STAGES: one `pod.lifecycle stages` line — clone · mirror · run come from the Pod's own
|
|
2542
3128
|
// `ELANOUS_POD_STAGE` markers (SI-POD-STAGE-MARKERS), read from that Pod's full named log. ⛔ No `--tail=400` fallback:
|
|
2543
3129
|
// a tail can lose the real `run start` and pair a child-printed look-alike with the real `run end` (review mf) —
|
|
@@ -2606,10 +3192,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2606
3192
|
appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-finished', data: { job: name, executionKey, fromChildRunId: retryFromChildRunId, childRunId: liveChildRunId, state } }, ledgerDir);
|
|
2607
3193
|
}
|
|
2608
3194
|
if (goalFile) {
|
|
3195
|
+
const noResultOutcome = state === 'failed' && containerExitCode !== null && noResultDiagnostic ? 'abandoned' : 'pod-no-result';
|
|
2609
3196
|
const record: GoalExecutionRecord = {
|
|
2610
3197
|
runId: liveChildRunId,
|
|
2611
3198
|
stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
|
|
2612
|
-
outcome: (!disposition ?
|
|
3199
|
+
outcome: (!disposition ? noResultOutcome : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
|
|
2613
3200
|
ok: state === 'complete' && (disposition?.ok ?? false),
|
|
2614
3201
|
...(disposition?.prNumber !== undefined ? { prNumber: disposition.prNumber } : {}),
|
|
2615
3202
|
completedAt: new Date().toISOString(),
|
|
@@ -2619,16 +3206,41 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2619
3206
|
}
|
|
2620
3207
|
// The host still has the complete Pod log here. The CLI only retains a short stdout tail,
|
|
2621
3208
|
// so preserve the parsed child facts in the parent ledger before that tail is returned.
|
|
2622
|
-
if (state === 'failed' || state === 'aborted') {
|
|
3209
|
+
if (state === 'failed' || state === 'aborted' || noResultLogEvidence || (state === 'complete' && noResultDiagnostic !== null)) {
|
|
2623
3210
|
// Every terminal attempt writes a marker, including one with no parseable child facts.
|
|
2624
3211
|
// The next attempt must never inherit an earlier Pod's classification.
|
|
2625
|
-
|
|
3212
|
+
const childEvidence = noResultLogEvidence ?? disposition?.childEvidence ?? null;
|
|
3213
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence, childRunId: liveChildRunId, job: name, ...(podTermination ? { podTermination } : {}) } }, ledgerDir); }
|
|
2626
3214
|
catch (error) { debug.log('self-implement.pod', 'child-evidence-write-failed', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
2627
3215
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
3216
|
+
let prLookup: OpenPrByBranch | undefined;
|
|
3217
|
+
const terminalBranch = typeof disposition?.branch === 'string' ? disposition.branch.trim() : '';
|
|
3218
|
+
if (disposition && (state === 'failed' || state === 'aborted' || disposition.ok === false)
|
|
3219
|
+
&& (!Number.isSafeInteger(disposition.prNumber) || (disposition.prNumber ?? 0) <= 0) && terminalBranch) {
|
|
3220
|
+
try { prLookup = await (options.lookupOpenPrByBranch ?? defaultLookupOpenPrByBranch)(terminalBranch, repoUrl); }
|
|
3221
|
+
catch { prLookup = { status: 'failed', reason: 'pr-lookup-threw' }; }
|
|
3222
|
+
}
|
|
3223
|
+
const terminalDisposition = prLookup?.status === 'found' && disposition
|
|
3224
|
+
? { ...disposition, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl } : disposition;
|
|
3225
|
+
if (prLookup?.status === 'found') debug.log('self-implement.pod', 'pr-terminal-bound-by-branch', {
|
|
3226
|
+
branch: terminalBranch, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl, status: 'found', job: name });
|
|
3227
|
+
else if (prLookup) debug.log('self-implement.pod', 'pr-terminal-branch-lookup', {
|
|
3228
|
+
branch: terminalBranch, status: prLookup.status, reason: prLookup.reason, job: name });
|
|
3229
|
+
const terminalRecord = { ...classifyPodTerminal({ childRunId: liveChildRunId, state, disposition: terminalDisposition, env,
|
|
3230
|
+
prBody: options.draftResidue || prLookup?.status === 'found' ? undefined : podTerminalPrBody(terminalDisposition),
|
|
3231
|
+
goalText: parentGoal ?? input.feature, at: new Date().toISOString() }),
|
|
3232
|
+
...(prLookup ? { prLookup: prLookup.status === 'found' ? { status: 'found', source: 'branch-lookup' }
|
|
3233
|
+
: { status: prLookup.status, reason: prLookup.reason } } : {}),
|
|
3234
|
+
...(prLookup?.status === 'found' ? { prNumberSource: 'branch-lookup' } : {}) };
|
|
3235
|
+
try {
|
|
3236
|
+
if (options.draftResidue) await options.draftResidue(terminalRecord, { ledgerRunId, ledgerDir });
|
|
3237
|
+
else if (prLookup?.status === 'found') {
|
|
3238
|
+
// This lookup only repairs the host ledger's PR identity. The draft sweeper owns PR writes.
|
|
3239
|
+
const alreadyRecorded = loadRunLedger(ledgerRunId, ledgerDir)?.some((entry) =>
|
|
3240
|
+
entry.event === 'pr-terminal' && entry.data.childRunId === liveChildRunId);
|
|
3241
|
+
if (!alreadyRecorded) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pr-terminal', data: terminalRecord }, ledgerDir);
|
|
3242
|
+
} else await applyPodTerminal(terminalRecord, { ledgerRunId, ledgerDir });
|
|
3243
|
+
}
|
|
2632
3244
|
catch (error) { debug.log('draft.residue', 'failed', { childRunId: liveChildRunId, prNumber: terminalRecord.prNumber,
|
|
2633
3245
|
terminalClass: terminalRecord.terminalClass, owner: terminalRecord.owner, ownerSource: terminalRecord.ownerSource,
|
|
2634
3246
|
step: 'apply', reason: error instanceof Error ? error.message : String(error) }); }
|
|
@@ -2648,9 +3260,15 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2648
3260
|
const finishedDisposition = deadlineExceeded && disposition
|
|
2649
3261
|
? { ...disposition, failureClassification: 'run-deadline-exceeded' as const }
|
|
2650
3262
|
: disposition;
|
|
2651
|
-
|
|
3263
|
+
// A gate environment deficit can exit the child with 1 while still yielding a
|
|
3264
|
+
// verified repair bundle. The host rejudges the new PR head after CAS apply.
|
|
3265
|
+
const podExitCode = repairPodExitCode({ state, repairCommitOnly: !!options.repairCommitOnly,
|
|
3266
|
+
repairArtifactReady, disposition });
|
|
2652
3267
|
// SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
|
|
2653
|
-
const
|
|
3268
|
+
const rawPodReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
|
|
3269
|
+
const podReason = rawPodReason
|
|
3270
|
+
? redactSecretText(noResultLogEvidence ? boundedPodLogText(rawPodReason, POD_NO_RESULT_LOG_REASON_MAX_CHARS) : rawPodReason)
|
|
3271
|
+
: undefined;
|
|
2654
3272
|
if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
|
|
2655
3273
|
if (podExitCode === 1) {
|
|
2656
3274
|
// HARNESS-FULL-GRAPH — 실패 쪽 두 노드: 결과 해석(result-parse) → 수확(salvage, Pod Job 이 push 한 줄로 판정).
|
|
@@ -2668,7 +3286,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2668
3286
|
? { error: { code: 'pod-gh-login-failed', message: `Job ${name} GitHub 앱 로그인 실패 (container=${containerReason ?? 'unknown'}/7, attempt=${ghLoginRetried ? 2 : 1}) — 새 토큰으로 ${ghLoginRetried ? '1회 재시도했으나 다시 실패' : '재시도 불가'}` } }
|
|
2669
3287
|
: oomKilled
|
|
2670
3288
|
? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})${oomRetried || (memoryGi(reattachedMemoryLimit ?? memoryLimit) ?? 0) >= (memoryGi(podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit) ?? Infinity) ? ` — OOM · ${oomRetried ? retryTier : 'high'} 에서도` : ''}\n${formatLastMemSample(samples.at(-1))}` } }
|
|
2671
|
-
|
|
3289
|
+
: { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : disposition?.stage ? ` — childStage=${disposition.stage} · reason=${podReason}` : ` — childError=no-result-line · reason=${podReason}`}${noResultDiagnostic && containerExitCode !== null ? ` · ${noResultDiagnostic}` : ''}` } }
|
|
2672
3290
|
: podReason && !childFailure?.error ? { error: { code: 'pod-child-failed', message: podReason } } : {}),
|
|
2673
3291
|
...(finishedDisposition ? { disposition: finishedDisposition } : {}),
|
|
2674
3292
|
};
|
|
@@ -2736,7 +3354,47 @@ export function recordPodSalvage(logs: string, job: string, log: (category: stri
|
|
|
2736
3354
|
return rows.map((row) => row.branch);
|
|
2737
3355
|
}
|
|
2738
3356
|
|
|
2739
|
-
|
|
3357
|
+
type PodPrOpenFailure = {
|
|
3358
|
+
childErrorTail: string;
|
|
3359
|
+
prFailureStage: 'pr-open-failed';
|
|
3360
|
+
prFailureSource: 'child-ledger' | 'terminal-result';
|
|
3361
|
+
blockingPrNumber?: number;
|
|
3362
|
+
candidateCardId?: string;
|
|
3363
|
+
blockingCardId?: string;
|
|
3364
|
+
};
|
|
3365
|
+
|
|
3366
|
+
/** Preserve the actionable tail independently of the bounded human error. Never infer a card from a PR number. */
|
|
3367
|
+
function podPrOpenFailure(ledger: Record<string, unknown> | undefined | null, terminalError: string): PodPrOpenFailure | null {
|
|
3368
|
+
const ledgerError = typeof ledger?.error === 'string' ? ledger.error : '';
|
|
3369
|
+
// A previous rework round can leave a pr-open-failed row behind. Only a terminal PR failure may consume it.
|
|
3370
|
+
if (!/draft PR 생성 실패|PR (?:gh|guard) 실패|same-cell reuse:/u.test(terminalError)) return null;
|
|
3371
|
+
// The terminal must contain the complete ledger error. A shared prefix such as "same-cell"
|
|
3372
|
+
// cannot distinguish an earlier rework round from the current failed attempt.
|
|
3373
|
+
const matchingLedger = ledgerError.length >= 20 && terminalError.includes(ledgerError);
|
|
3374
|
+
const source = terminalError || ledgerError;
|
|
3375
|
+
const sanitized = redactSecretText(source).replace(/\/home\/[^/\s]+\//gu, '~/');
|
|
3376
|
+
const tail = sanitized.split(/\r\n|\n|\r/u).filter(Boolean).at(-1) ?? sanitized;
|
|
3377
|
+
const terminalHasIds = /(?:blockers=|blockingPr=|candidateCardId=)/u.test(terminalError);
|
|
3378
|
+
const detail = `${terminalError}\n${matchingLedger ? ledgerError : ''}`;
|
|
3379
|
+
const blocker = /(?:blockers=|blockingPr=)\s*#?(\d+)(?::([A-Za-z0-9_-]{1,128}))?/u.exec(detail);
|
|
3380
|
+
const prNumber = blocker ? Number(blocker[1]) : null;
|
|
3381
|
+
const candidate = /candidateCardId=([A-Za-z0-9_-]{1,128})/u.exec(detail)?.[1];
|
|
3382
|
+
const card = blocker?.[2];
|
|
3383
|
+
return {
|
|
3384
|
+
childErrorTail: tail.slice(-240),
|
|
3385
|
+
prFailureStage: 'pr-open-failed',
|
|
3386
|
+
prFailureSource: terminalHasIds || !matchingLedger ? 'terminal-result' : 'child-ledger',
|
|
3387
|
+
...(prNumber && Number.isSafeInteger(prNumber) ? { blockingPrNumber: prNumber } : {}),
|
|
3388
|
+
...(candidate && candidate !== 'unknown' ? { candidateCardId: candidate } : {}),
|
|
3389
|
+
...(card && card !== 'unknown' ? { blockingCardId: card } : {}),
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
function lastPodChildFailure(logs: string): { stage: string; error: string; rawError: string } | null {
|
|
3394
|
+
// Keep canonical duplicate-cap attribution while preserving the raw terminal error
|
|
3395
|
+
// used by PR-open failure diagnosis below.
|
|
3396
|
+
const attribution = podChildFailureAttribution(logs);
|
|
3397
|
+
if (!attribution) return null;
|
|
2740
3398
|
for (const line of logs.split('\n').reverse()) {
|
|
2741
3399
|
if (!line.trimStart().startsWith('{')) continue;
|
|
2742
3400
|
let result: unknown;
|
|
@@ -2746,14 +3404,65 @@ function lastPodChildFailure(logs: string): { stage: string; error: string } | n
|
|
|
2746
3404
|
const terminal = podTerminalRow(row);
|
|
2747
3405
|
if (!terminal) continue;
|
|
2748
3406
|
if (terminal.ok !== false || typeof terminal.error !== 'string') return null;
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const last = lines.at(-1)!.slice(0, 240);
|
|
2752
|
-
return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}` };
|
|
3407
|
+
if (terminal.stage !== attribution.stage) return null;
|
|
3408
|
+
return { ...attribution, error: redactSecretText(attribution.error), rawError: terminal.error };
|
|
2753
3409
|
}
|
|
2754
3410
|
return null;
|
|
2755
3411
|
}
|
|
2756
3412
|
|
|
3413
|
+
/** POD-NORESULT-LOG-TAIL: how many container-log lines a failed Pod leaves behind for the parent. */
|
|
3414
|
+
export const POD_NO_RESULT_LOG_TAIL_LINES = 80;
|
|
3415
|
+
const POD_NO_RESULT_LOG_TAIL_LINE_CHARS = 512;
|
|
3416
|
+
const POD_NO_RESULT_LOG_TAIL_MAX_CHARS = 8_192;
|
|
3417
|
+
const POD_NO_RESULT_LOG_REASON_MAX_CHARS = 512;
|
|
3418
|
+
|
|
3419
|
+
function boundedPodLogText(source: string, maxChars: number): string {
|
|
3420
|
+
const redacted = redactSecretText(source).replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
3421
|
+
return redacted.length > maxChars ? `${redacted.slice(0, maxChars)}…[truncated]` : redacted;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
/** 로그 꼬리 — 마지막 N줄만, 한 줄씩 비밀값 마스킹. */
|
|
3425
|
+
export function podNoResultLogTailLines(source: string, maxLines: number = POD_NO_RESULT_LOG_TAIL_LINES): string[] {
|
|
3426
|
+
const lines = source.replace(/\r\n/g, '\n').split('\n');
|
|
3427
|
+
while (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
3428
|
+
const result: string[] = [];
|
|
3429
|
+
let chars = 0;
|
|
3430
|
+
for (const line of lines.slice(-Math.max(0, maxLines)).reverse()) {
|
|
3431
|
+
if (chars >= POD_NO_RESULT_LOG_TAIL_MAX_CHARS) break;
|
|
3432
|
+
const bounded = boundedPodLogText(line, Math.min(POD_NO_RESULT_LOG_TAIL_LINE_CHARS, POD_NO_RESULT_LOG_TAIL_MAX_CHARS - chars));
|
|
3433
|
+
if (!bounded) continue;
|
|
3434
|
+
result.push(bounded);
|
|
3435
|
+
chars += bounded.length;
|
|
3436
|
+
}
|
|
3437
|
+
return result.reverse();
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
export type PodNoResultLogRead = { tail: string[] } | { unavailable: string };
|
|
3441
|
+
|
|
3442
|
+
/**
|
|
3443
|
+
* POD-NORESULT-LOG-TAIL: the terminal Job (`backoffLimit 0`) is collected right after it fails, so the
|
|
3444
|
+
* container log has to be read while the Pod still exists. Never throws — an unreadable log becomes a reason.
|
|
3445
|
+
*/
|
|
3446
|
+
export function readPodNoResultLogTail(args: {
|
|
3447
|
+
run: (args: readonly string[]) => { status: number | null; stdout: string; stderr: string };
|
|
3448
|
+
namespace: string;
|
|
3449
|
+
job: string;
|
|
3450
|
+
maxLines?: number;
|
|
3451
|
+
}): PodNoResultLogRead {
|
|
3452
|
+
const maxLines = args.maxLines ?? POD_NO_RESULT_LOG_TAIL_LINES;
|
|
3453
|
+
try {
|
|
3454
|
+
const result = args.run(['-n', args.namespace, 'logs', `job/${args.job}`, `--tail=${maxLines}`]);
|
|
3455
|
+
if (result.status !== 0) {
|
|
3456
|
+
const reason = boundedPodLogText(result.stderr || result.stdout || '', POD_NO_RESULT_LOG_REASON_MAX_CHARS);
|
|
3457
|
+
return { unavailable: reason || `kubectl logs exit ${result.status}` };
|
|
3458
|
+
}
|
|
3459
|
+
const tail = podNoResultLogTailLines(result.stdout, maxLines);
|
|
3460
|
+
return tail.length ? { tail } : { unavailable: 'empty log output' };
|
|
3461
|
+
} catch (error) {
|
|
3462
|
+
return { unavailable: boundedPodLogText(error instanceof Error ? error.message : String(error), POD_NO_RESULT_LOG_REASON_MAX_CHARS) || 'unknown error' };
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
|
|
2757
3466
|
/** 사람용 런 결과 한 줄 — 수확 브랜치가 있으면 «수확할 브랜치: …» 를 붙인다. */
|
|
2758
3467
|
export function podRunResultLine(logs: string, branches: readonly string[]): string {
|
|
2759
3468
|
const result = logs.split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')).join('\n').slice(-4000);
|