elanous 0.2.32 → 0.2.35
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/{IVKr3AlRUVLKsw2fh7eXF → T1mBQqmkwKDLGtyisDCOd}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{997ce8a5-2ad501927003b41b.js → 0368f25d-ad84af92fefee68f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1165.bad13fc9151a7288.js +1 -0
- package/apps/pwa/out/_next/static/chunks/13.02a07436dec15f89.js +32 -0
- package/apps/pwa/out/_next/static/chunks/{5525.86debf231e181813.js → 1530.0e612369b6fd7ea0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +41 -0
- package/apps/pwa/out/_next/static/chunks/{7391.ebae168025c91b6a.js → 1674.2a65fd5668e1cc5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +24 -0
- package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +74 -0
- package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7947-d0759430f8042240.js → 1993-c0007b7ad0c0ef41.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{60fb96c9.360b01d8d4927b7b.js → 1c735de4.f993a30bbd3c57c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1dc5a567.9b092bfae3d7099b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5307.60f2209e809bd627.js → 2018.a741297bce759490.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +148 -0
- package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3902.e1e71aaac850a045.js → 2625.5d870aa999f33db1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +77 -0
- package/apps/pwa/out/_next/static/chunks/2833-0fe47af99ed13c29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +62 -0
- package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +90 -0
- package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4174.92ed04922709ac9e.js → 3408.21d1570602f4b6c0.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3589.5444a06bcf6e775c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/359-2250789a2d3499e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +36 -0
- package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9398-6aed28f0a96d0d6f.js → 3677-4215f826f6aa68e5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +79 -0
- package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3f7c4ee5.3dde54f01a816613.js +122 -0
- package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7371.80c05c653b08e907.js → 4346.8601952e73bf5803.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4441-875b97f5821ed58a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +286 -0
- package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{2940-619bfd8ef337c611.js → 4705-194e64e47ca0fbdb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1335-6731f392c35216eb.js → 4718-17d2af636796a330.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7502.ccd5b8065545edd0.js → 5101.e32282718f99ed6f.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{2001.6c7e0fd02190886c.js → 5154.a118f3957ed14e36.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +23 -0
- package/apps/pwa/out/_next/static/chunks/{5173-233188df2f42c626.js → 5483-dd5d7697f6bdb106.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5637.281e5dec4ea6ecf1.js +94 -0
- package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3267c0e6.d34854e7b3984a6e.js → 56d372f2.681ab4ec4c998fae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5852.db1f7449f4f74612.js +287 -0
- package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +1 -0
- package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6433.fc573af22bbc9ab7.js → 6236.e18cced2d5ba6afb.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +8 -0
- package/apps/pwa/out/_next/static/chunks/630.fe5a46428d63b013.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +27 -0
- package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +93 -0
- package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6866-79aa5b4e47cac6aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6544cb48.a87fe20ddf137be2.js → 6b89c8bd.24df687a820500b3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +149 -0
- package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +104 -0
- package/apps/pwa/out/_next/static/chunks/{2871.0bca6137343b3f76.js → 7385.50a308a477bdfc21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2985.39ccdecd36848b12.js → 7644.17e37ba6f1d4226e.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8018.a32663f04a6fc5f9.js → 8219.baa715d1deee80e2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4111.2bfedbb233ad206b.js → 8256.e3ac8421a71f82f4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8437-8d5492094b166cfa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2213.2ccbada92c33ea0f.js → 8444.4c26b379ca2b9596.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +226 -0
- package/apps/pwa/out/_next/static/chunks/8557-e7898769499cd56f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +64 -0
- package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6092.a7f99e68b188ca66.js → 8637.47f5b35afcae5e6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3278.2e5d15b53a4091c8.js → 9214.63a510d39178e997.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +115 -0
- package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9221.7d22eaf353a2d4e7.js → 938.66ad1ef0e5abae1c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6157.0c9af021de4fb31b.js → 9512.5a76207a730e0491.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{69-0a2c8209c6d4a158.js → 967-2aa39b2d04b4c690.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7862.30a8d6482bb238eb.js → 9952.5daf5f94063a2b3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a27b1e5a0d1b308f.js → page-406b47745b53f269.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-26e6c18a1a750cf6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-32866485913d6a04.js → page-47c5d4db587266bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1f5a60f70cb4dd0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-bac7dee94e5eae4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-04d00ae9b0890349.js → page-dc0a532f5b9196ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7a1887c4.f36b88fba31da8fd.js → b2f6ba4d.fb56fdbbf798df33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/b6705a4f-b9a9047de9eecfd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b313b242.cb1265eea908db61.js → cbe38a15.5a032598e39970c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{409f1e57.d0649fc47691ebfe.js → d06f9160.a3d9062f95de8ad7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{0bb0ff36.b8d418980951e5ba.js → da5176c6.6773b6c5994e1d99.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +1 -0
- 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 +10 -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/README.md +5 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/default-loop.yaml +76 -5
- package/graphs/document-loop.yaml +71 -4
- package/graphs/implement-loop.yaml +59 -4
- package/graphs/journey/SCENARIOS.md +33 -0
- package/graphs/journey/recipes.yaml +1 -0
- package/graphs/journey/ta-journey.yaml +94 -0
- package/graphs/journey/units/author/author.yaml +20 -0
- package/graphs/journey/units/author/recipes.yaml +2 -0
- 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/journey/units/green/green.yaml +20 -0
- package/graphs/journey/units/green/recipes.yaml +2 -0
- package/graphs/journey/units/pr/pr.yaml +20 -0
- package/graphs/journey/units/pr/recipes.yaml +2 -0
- package/graphs/journey/units/remember/recipes.yaml +2 -0
- package/graphs/journey/units/remember/remember.yaml +20 -0
- package/graphs/operate-loop.yaml +71 -4
- package/graphs/overlays/stop-heal-ladder.yaml +1 -1
- package/graphs/release-rehearsal/recipes.yaml +1 -1
- package/graphs/release-rehearsal/release-rehearsal.yaml +2 -2
- package/graphs/research-loop.yaml +159 -4
- package/graphs/test-diet/test-diet.yaml +3 -0
- package/graphs/units/dispatch-pod/dispatch-pod.yaml +50 -0
- package/graphs/units/dispatch-pod/recipes.yaml +14 -0
- package/graphs/units/entry/entry.yaml +50 -0
- package/graphs/units/entry/recipes.yaml +14 -0
- package/graphs/units/harvest/harvest.yaml +25 -0
- package/graphs/units/harvest/recipes.yaml +4 -0
- package/graphs/units/land/land.yaml +45 -0
- package/graphs/units/land/recipes.yaml +3 -0
- package/graphs/units/recipes.yaml +7 -0
- package/graphs/units/recover.yaml +39 -0
- package/graphs/units/reflect/recipes.yaml +3 -0
- package/graphs/units/reflect/reflect.yaml +15 -0
- package/graphs/units/task-agent-recover-review.yaml +17 -0
- package/graphs/units/task-agent-recover-wait.yaml +17 -0
- package/graphs/units/verbs/make-doc.yaml +34 -0
- package/graphs/units/verbs/recipes.yaml +33 -0
- package/graphs/units/verbs/verify.yaml +55 -0
- package/graphs/work/recipes.yaml +2 -0
- package/graphs/work/research-report.yaml +28 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +69 -16
- package/scripts/coord-role-pack.ts +19 -6
- package/scripts/docs/generate-graph-anatomy.ts +19 -2
- package/scripts/graph-harvest-unit.ts +22 -0
- package/scripts/graph-units/journey-stub.ts +6 -0
- package/scripts/graph-units/land-enqueue.ts +78 -0
- package/scripts/graph-units/recover-plan.ts +15 -0
- package/scripts/graph-units/reflect-node.ts +88 -0
- package/scripts/graph-units/ta-journey-observe.ts +109 -0
- package/scripts/graph-units/verbs/headless-goal-loop.ts +152 -0
- package/scripts/graph-units/verbs/verify-stage.ts +75 -0
- package/scripts/graph-units/verbs-envelope.ts +139 -0
- package/scripts/hq/standby-snapshot.ts +30 -0
- package/scripts/public-export.ts +43 -1
- package/scripts/ratchets/train-side-invariants.sh +45 -0
- package/scripts/release-loop/auto-approve-node.ts +30 -16
- package/scripts/release-loop/docs-land-node.ts +21 -5
- package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
- package/scripts/release-loop/gate-closure.ts +5 -1
- package/scripts/release-loop/gate-diff.ts +1 -1
- package/scripts/release-loop/gate-node.ts +675 -49
- package/scripts/release-loop/gate-partial.ts +68 -14
- package/scripts/release-loop/gate-radius-live.ts +177 -0
- package/scripts/release-loop/gate-radius-shadow.ts +138 -0
- package/scripts/release-loop/gate-traffic-mode.ts +128 -0
- package/scripts/release-loop/gate-triage-node.ts +5 -2
- package/scripts/release-loop/known-issues-0.2.33-approved.json +194 -0
- package/scripts/release-loop/known-issues-node.ts +77 -8
- package/scripts/release-loop/node-verdict.ts +12 -1
- package/scripts/release-loop/npm-publish-node.ts +66 -12
- package/scripts/release-loop/preflight.ts +93 -7
- package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
- package/scripts/release-loop/publish-node.ts +60 -9
- 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 +56 -0
- package/scripts/release-loop/resume-release.ts +155 -19
- package/scripts/release-loop/resume.ts +24 -1
- package/scripts/release-loop/tui-sim-node.ts +8 -2
- package/scripts/release-loop/unattended-release.ts +10 -2
- package/scripts/test-diet/lib.ts +6 -1
- package/scripts/test-diet/node.ts +194 -13
- package/scripts/ux-sim.ts +39 -0
- package/src/acp/core-turn-bridge.ts +20 -5
- package/src/acp/reasoning-thought-buffer.ts +45 -0
- package/src/agent/self-ambient-cache.ts +8 -0
- package/src/agent-substrate/acp-reviewer.ts +90 -15
- package/src/agent-substrate/pr-reviewer.ts +17 -0
- package/src/agent-substrate/review-finding-key.ts +53 -0
- package/src/agent-substrate/self-review-cli.ts +188 -18
- package/src/ask-user-question/pending-questions.ts +1 -1
- package/src/autopilot/pr-manager.ts +393 -65
- package/src/budget/run-usage-rollup.ts +1 -0
- package/src/chat/index.ts +0 -2
- package/src/chat/pickers/modal-runtime.ts +10 -27
- package/src/chat/tool-render/dispatch.ts +62 -10
- package/src/cli/agent-cli.ts +13 -2
- package/src/cli/control-cli.ts +24 -7
- package/src/cli/doctor-cli.ts +4 -1
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/light-entry-route.ts +75 -0
- package/src/cli/llm-detect-cli.ts +23 -1
- package/src/cli/pod-cli.ts +77 -3
- package/src/cli/pr-cli.ts +81 -14
- package/src/cli/pr-land-base-read.ts +2 -0
- package/src/cli/pr-land-overlap-evidence.ts +154 -16
- package/src/cli/pr-land-pod-shadow-job.ts +3 -0
- package/src/cli/pr-land-recovery.ts +64 -0
- package/src/cli/pty-takeover-cli.ts +6 -5
- package/src/cli/release-checklist-read-cli.ts +79 -0
- package/src/cli/release-cli.ts +189 -132
- package/src/cli/release-run-if-ready.ts +29 -28
- package/src/cli/release-single-flight.ts +178 -0
- package/src/cli/remote-resolve.ts +1 -1
- package/src/cli/root-tui-entry.ts +48 -0
- package/src/cli/seat-cli.ts +37 -0
- package/src/cli/setup-cli.ts +40 -1
- package/src/cli/sim-cli.ts +22 -0
- package/src/cli/tasks-cli.ts +667 -14
- package/src/cli/usage-cli.ts +13 -11
- package/src/clipboard/history.ts +179 -13
- package/src/config.ts +8 -1
- package/src/control/control-status.ts +8 -3
- package/src/control-plane/ledger.ts +5 -3
- package/src/control-plane/server.ts +5 -3
- package/src/core-turn/short-question.ts +5 -1
- package/src/dashboard/chat/acp-chat.ts +8 -0
- package/src/dashboard/index.ts +114 -77
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +3 -0
- package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
- package/src/dashboard/input/seat-autowake.ts +56 -3
- package/src/dashboard/render/theme-resolver.ts +33 -4
- package/src/dashboard/seat-boot-preamble.ts +1 -1
- package/src/dashboard/slash-runtime/ask-clarification.ts +48 -0
- package/src/dashboard/slash-runtime/dashboard-delegated-slashes.ts +68 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +62 -322
- package/src/dashboard/slash-runtime/dashboard-log-slash.ts +208 -0
- package/src/dashboard/turn-preamble.ts +47 -1
- package/src/dashboard/turn-stream-formatter.ts +34 -1
- package/src/dashboard/turn-stream-runtime.ts +2 -0
- package/src/dashboard/turn-typeahead-prompt-zone.ts +67 -0
- package/src/domains/guardian-tick.ts +54 -2
- package/src/domains/outbound-alert.ts +3 -2
- package/src/domains/schedule-registry.ts +16 -2
- package/src/extractors.ts +3 -1
- package/src/git-fs/idle-refresh.ts +76 -0
- package/src/git-fs/index.ts +18 -7
- package/src/git-fs/retry.ts +4 -0
- package/src/git-fs/runner.ts +2 -0
- package/src/git-fs/worktree.ts +42 -4
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/runner.ts +267 -54
- package/src/grok/credential.ts +15 -3
- package/src/harness/harness-cli-command.ts +151 -38
- package/src/harness/harness-incidents.ts +37 -0
- package/src/harness/harness-pod-dispatch.ts +38 -1
- package/src/harness/harness-salvage-cli.ts +14 -2
- package/src/harness/launch-utterance-ledger.ts +208 -0
- 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 +683 -146
- package/src/intake-plane/already-built-hint.ts +91 -0
- package/src/intake-plane/requirement-funnel.ts +29 -1
- package/src/knowhow/pack.ts +199 -0
- package/src/light-cli.ts +111 -0
- 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.ts +268 -65
- package/src/loops/budget.ts +95 -0
- package/src/loops/msg-wake.ts +8 -2
- package/src/loops/orchestrator/traffic.ts +132 -1
- package/src/model-tier/llm-tier-map.ts +15 -1
- package/src/mss/logging/log-store.ts +4 -5
- package/src/native-tool-catalog.ts +15 -1
- package/src/nexus/api/graphs-api.ts +22 -3
- package/src/nexus/api/http-server.ts +1 -1
- package/src/nexus/api/loop-resources.ts +13 -1
- package/src/nexus/api/ops-api.ts +12 -0
- package/src/oauth/claude-pty-maturity.ts +66 -0
- package/src/oauth/codex-access-mirror.ts +55 -0
- package/src/oauth/codex.ts +7 -0
- package/src/oauth/fallback-chain.ts +1 -1
- package/src/onboarding/first-run.ts +5 -3
- package/src/onboarding/non-interactive.ts +64 -13
- package/src/onboarding.ts +10 -2
- package/src/ops/overview-collect.ts +54 -4
- package/src/ops/overview.ts +31 -7
- package/src/pr-tool/census.ts +282 -30
- package/src/pr-tool/inflight-registry.ts +160 -7
- package/src/prompt-library/gpt6-family-addendum.ts +14 -1
- package/src/pty-shell/herdr-agent-report.ts +442 -0
- package/src/pty-shell/pty-control-ipc.ts +5 -5
- package/src/pty-shell/pty-manifest.ts +7 -0
- package/src/pty-shell/pty-write-outcome.ts +24 -6
- package/src/pty-shell/tui-run-state.ts +117 -3
- package/src/release-loop/checklist.ts +103 -5
- package/src/release-loop/feature-store.ts +19 -2
- package/src/release-loop/landed-but-yellow.ts +129 -5
- package/src/release-loop/light-rc.ts +1 -0
- package/src/release-loop/placement.ts +10 -3
- package/src/release-loop/rehearsal.ts +205 -25
- package/src/release-loop/rubric.ts +16 -2
- package/src/repl/index.ts +174 -17
- package/src/repl/ops-msg-bridge.ts +32 -0
- package/src/self-dev/ask-launch-flow.ts +44 -5
- package/src/self-dev/cli-help-text.ts +17 -0
- package/src/self-dev/dev-cli.ts +11 -9
- package/src/self-dev/draft-residue.ts +37 -1
- package/src/self-dev/draft-sweep.ts +188 -13
- package/src/self-dev/draft-triage-rules.ts +107 -0
- package/src/self-dev/fleet-reduce.ts +3 -2
- package/src/self-dev/graph-front-nodes.ts +112 -0
- package/src/self-dev/graph-journey-nodes.ts +11 -6
- package/src/self-dev/graph-journey-route.ts +60 -6
- 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.ts +79 -3
- package/src/self-dev/review-provider-fallback.ts +35 -1
- package/src/self-dev/run-supervisor.ts +107 -12
- package/src/self-dev/run-types.ts +5 -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-implement/blocked-draft-policy.ts +20 -0
- package/src/self-implement/classify-shadow.ts +3 -7
- package/src/self-implement/doctor-visit.ts +18 -1
- package/src/self-implement/gate-baseline.ts +95 -12
- package/src/self-implement/gate-cli.ts +1 -0
- package/src/self-implement/gate-remote.ts +40 -25
- package/src/self-implement/goal-author-decompose.ts +275 -47
- package/src/self-implement/goal-author-identity.ts +66 -0
- package/src/self-implement/goal-author-preservation-obligation.ts +35 -0
- package/src/self-implement/goal-author.ts +254 -120
- package/src/self-implement/goal-elaborate.ts +295 -0
- package/src/self-implement/goal-refine.ts +270 -0
- package/src/self-implement/graph-overlay-yaml.ts +28 -2
- package/src/self-implement/graph-unit-flatten.ts +103 -0
- package/src/self-implement/graph-unit-resolver.ts +107 -0
- package/src/self-implement/graph-yaml.ts +151 -1
- package/src/self-implement/headless-elanous-driver.ts +1 -0
- package/src/self-implement/knowhow-pack.ts +103 -0
- package/src/self-implement/orchestrator.ts +160 -45
- package/src/self-implement/pod-warm-pool-profile.ts +45 -0
- package/src/self-implement/review-mustfix-class.ts +203 -0
- package/src/self-implement/rework-budget-judgment.ts +5 -8
- package/src/self-implement/run-stop.ts +17 -2
- package/src/self-implement/running-runs.ts +29 -1
- package/src/self-implement/scope-adjacency.ts +113 -0
- package/src/self-implement/seams.ts +138 -4
- package/src/self-implement/stop-autoheal.ts +112 -2
- package/src/session/chat.ts +27 -0
- package/src/session-runtime/index.ts +1 -1
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/setup/llm-env-detect.ts +5 -0
- package/src/skills/tools/agent.ts +140 -14
- 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 +66 -17
- package/src/steward/hold-hygiene.ts +24 -6
- package/src/steward/release-day.ts +7 -3
- package/src/steward/triage-budget.ts +194 -0
- package/src/steward/triage.ts +57 -7
- package/src/task-agent/actions.ts +69 -1
- package/src/task-agent/green-sweep.ts +4 -0
- package/src/task-agent/harvest-unit.ts +141 -0
- package/src/task-agent/live-moves.ts +257 -15
- package/src/task-agent/parent-host.ts +216 -6
- package/src/task-agent/postland-card.ts +80 -1
- package/src/task-agent/rejudge-on-head.ts +112 -26
- package/src/task-agent/repair-branch-cas.ts +4 -1
- package/src/task-agent/repair-cycle.ts +16 -3
- package/src/task-agent/run-identity.ts +4 -3
- package/src/task-agent/shadow.ts +19 -3
- package/src/task-agent/task-hand.ts +96 -36
- package/src/task-agent/tier-split.ts +127 -0
- package/src/task-agent/work-order-build.ts +24 -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 +72 -3
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-command-job.ts +47 -8
- package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
- package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +86 -8
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +8 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +73 -17
- package/src/task-orchestrator/surfaces/pod-provenance.ts +76 -0
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +72 -4
- package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +32 -5
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +3 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +421 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +2 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +588 -71
- package/src/task-orchestrator/surfaces/self-implement.ts +106 -2
- package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
- package/src/tool-runtime/truncation-store.ts +4 -0
- package/src/tui/README.md +2 -0
- package/src/tui/chat/background-job-status.ts +54 -0
- package/src/tui/chat/log-view.ts +80 -0
- package/src/tui/entry.ts +406 -0
- package/src/tui/input/composer.ts +144 -0
- package/src/tui/seat-mail-boot.ts +59 -0
- package/src/tui/slash/compact.ts +100 -0
- package/src/tui/slash/new-session.ts +35 -0
- package/src/tui-client/dashboard-session.ts +10 -1
- package/src/tui-client/local-chat-turn.ts +239 -0
- package/src/tui-client/local-harness-command.ts +63 -0
- package/src/tui-client/local-provider-command.ts +184 -0
- package/src/user-config/coercion.ts +36 -0
- package/src/user-config.ts +22 -43
- package/src/ux-sim/event-monkey.ts +378 -0
- package/src/ux-sim/fake-bin.ts +42 -7
- package/src/ux-sim/fault-nextmove-dispatch.ts +266 -0
- package/src/ux-sim/fault-pod-codex-503.ts +211 -0
- package/src/ux-sim/fault-scope-escape-lift.ts +189 -0
- package/src/ux-sim/scenario.ts +72 -13
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +0 -27
- package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +0 -287
- package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +0 -62
- package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1654.32de4b324981a188.js +0 -23
- package/apps/pwa/out/_next/static/chunks/1834-98b64172bc1efe77.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1913.7cc6fb6055b1ed3f.js +0 -41
- package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2112-e4efc21a6505d5bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2534.e85ae21ad0dac829.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2898.fd781bc7cf01a7b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +0 -115
- package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +0 -64
- package/apps/pwa/out/_next/static/chunks/3541-ce3a5424ed5ad7f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3583.a05e88cc8d594628.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +0 -36
- package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +0 -77
- package/apps/pwa/out/_next/static/chunks/4745.67d6f99c245af3f7.js +0 -226
- package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4999.c064a4000c2a9e94.js +0 -79
- package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5304.04d24756f242a094.js +0 -148
- package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5458-16cd7a2b51b74c8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5543.a438b8ab5a13ea47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +0 -74
- package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +0 -94
- package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6323.8672b4817a306928.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6517.4147878ac06d584d.js +0 -90
- package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +0 -32
- package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +0 -104
- package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6767.ea607cf0c20a25af.js +0 -24
- package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +0 -93
- package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +0 -8
- package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +0 -1
- package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9002.e5658224e3dc1b6f.js +0 -24
- package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9138.b6e80031afb1d973.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9279-d28c5b7754d010ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9407.e49124dedf0f5e72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989.3694300e5f000b14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a9717a67c2eecbaf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c0f1ca371f710b06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-46430d45a9e04eac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-8ea49a4c6dd2c6e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-0e5a41fac1c75396.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-31aaf1ed37c6df9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c69247e7134d329d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-66dc5cd3a0999abe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-d256f5d5d9552823.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-ce8f1907b8a6101c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7607cca9b84950c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-eeab86176b03dc3b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5b93dbae3432b996.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f47e768b5b242b5a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-95306f892aa2d0ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-940fb6c217f6b470.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-8c66a3b24a745630.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7b675dfd35c36fc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8bd0d3cf7665bcfc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c219f2ed171131be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-40f8fa21bdae955b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-468f8313e4f1fbd9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-4214b855d9233ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-936752c7aa58762e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ec052ad359df1797.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-fad650e4793adb65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/e5012ffe.f727e23c231f4342.js +0 -122
- package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ebc7d829.264a0bcb6c750b50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +0 -1
- package/src/cli/g6a-registration-probe.ts +0 -16
- /package/apps/pwa/out/_next/static/{IVKr3AlRUVLKsw2fh7eXF → T1mBQqmkwKDLGtyisDCOd}/_ssgManifest.js +0 -0
|
@@ -12,22 +12,26 @@
|
|
|
12
12
|
// 부작용(kubectl·파일)은 주입받는다 — 시험은 가짜 kubectl 로 누른다.
|
|
13
13
|
|
|
14
14
|
import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skills.js';
|
|
15
|
-
import { emitPodLifecycle, parseSiTaskPodStages, siTaskPodLifecycleEvent, siTaskPodStageShell } from './pod-lifecycle-stages.js';
|
|
15
|
+
import { emitPodLifecycle, parseSiTaskDepsSkipped, parseSiTaskPodStages, siTaskPodK8sTiming, siTaskPodLifecycleEvent, siTaskPodStageShell } from './pod-lifecycle-stages.js';
|
|
16
16
|
import { podSourceScript, type PodSource } from './pod-source-receive.js';
|
|
17
17
|
import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroundingRun, type GroundingTokenScope } from '../../grounding/token.js';
|
|
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';
|
|
24
26
|
import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
|
|
27
|
+
import { createCodexAccessMirror } from '../../oauth/codex-access-mirror.js';
|
|
25
28
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
26
29
|
import { controlInboxEnv } from '../../harness/control-inbox.js';
|
|
27
30
|
import { finishPodFragment, writePodFragment } from '../../harness/self-send-target.js';
|
|
28
31
|
import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
|
|
29
32
|
import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
|
|
30
33
|
import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
34
|
+
import { runSuffixKey } from '../../self-implement/salvage-branches.js';
|
|
31
35
|
import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
|
|
32
36
|
import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
33
37
|
import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
|
|
@@ -67,7 +71,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
|
67
71
|
import { findGitDir } from '../../git-fs/locate.js';
|
|
68
72
|
import { hostLeaseBaseDir } from '../../pod-lease/host-lease.js';
|
|
69
73
|
import { acquireLockAsync } from '../../storage/file-lock.js';
|
|
70
|
-
import { debug } from '../../debug/log.js';
|
|
74
|
+
import { debug, redactSecretText } from '../../debug/log.js';
|
|
71
75
|
import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-dev/graph-journey-nodes.js';
|
|
72
76
|
import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
|
|
73
77
|
import { selectLiveDetail } from '../../live/detail-switch.js';
|
|
@@ -97,8 +101,16 @@ import { classifyPodExit, extractPodFailureReason, podTerminalRow, type PodExitC
|
|
|
97
101
|
import { kubectlTransientReason, retryKubectlTransient } from './kubectl-transient-retry.js';
|
|
98
102
|
import { OLD_DOOR_STAMP_ENV } from '../../self-dev/old-door.js';
|
|
99
103
|
import { resolveDefaultSourceHead, seedMirrorOnce, type MirrorSeedResult } from './pod-mirror-seed.js';
|
|
104
|
+
import { decideWarmReuse, type WarmReuseEvidenceByConsumer, type WarmReuseSwitchConfig } from './pod-warm-switch.js';
|
|
105
|
+
import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
|
|
106
|
+
import type { WarmPodTransportResult } from './pod-warm-worker-pod.js';
|
|
100
107
|
|
|
101
108
|
export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
109
|
+
type OpenPrByBranch = { status: 'found'; prNumber: number; prUrl: string }
|
|
110
|
+
| { status: 'not-found' | 'failed'; reason: string };
|
|
111
|
+
|
|
112
|
+
/** TC supplies the validated candidate/lease transport. UX only forwards its request command. */
|
|
113
|
+
export type WarmPodRequestHook = (request: { command: WarmWorkerCommand }) => Promise<WarmPodTransportResult> | WarmPodTransportResult;
|
|
102
114
|
|
|
103
115
|
// gh auth login may reject installation tokens when validating /user. Its config is
|
|
104
116
|
// still the credential source for gh and setup-git's helper. Install it privately
|
|
@@ -148,6 +160,11 @@ export interface PodSpawnOptions {
|
|
|
148
160
|
seedMirror?: (o: { host: string; commit: string; cwd: string }) => Promise<MirrorSeedResult | undefined>;
|
|
149
161
|
/** Git tree that has the pinned commit for the mirror seed (default: process.cwd()). */
|
|
150
162
|
mirrorSeedCwd?: string;
|
|
163
|
+
/** TA warm-reuse shadow evidence/config seams. Missing evidence must stay off; no warm candidate is consumed here. */
|
|
164
|
+
warmReuseConfig?: () => WarmReuseSwitchConfig | undefined;
|
|
165
|
+
warmReuseEvidence?: () => WarmReuseEvidenceByConsumer | undefined;
|
|
166
|
+
/** Optional warm-pool observation/preparation hook. It never replaces the feature implementation Job. */
|
|
167
|
+
warmPodRequest?: WarmPodRequestHook;
|
|
151
168
|
/** SI-SEED-DEFAULT — resolves a default source's branch tip for the mirror seed (test seam · default
|
|
152
169
|
* {@link resolveDefaultSourceHead}; none with an injected kubectl unless given). */
|
|
153
170
|
resolveSourceHead?: (o: { repoUrl: string; base?: string; cwd: string }) => Promise<{ sha?: string; reason?: string }>;
|
|
@@ -157,6 +174,8 @@ export interface PodSpawnOptions {
|
|
|
157
174
|
authorConfig?: () => UserConfig;
|
|
158
175
|
/** Host fallback chain seam; a Pod has no access to the host config. */
|
|
159
176
|
fallbackConfig?: () => UserConfig;
|
|
177
|
+
/** Host OpenGateway key config seam; unit tests inject isolated config without bypassing production reads. */
|
|
178
|
+
openGatewayConfig?: () => UserConfig;
|
|
160
179
|
/** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
|
|
161
180
|
source?: PodSource;
|
|
162
181
|
/** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
|
|
@@ -193,6 +212,8 @@ export interface PodSpawnOptions {
|
|
|
193
212
|
taskAgentLiveMoves?: () => Promise<ReadonlySet<string>> | ReadonlySet<string>;
|
|
194
213
|
/** PR comment for host-regate failures that never reached runHostRegate (injectable for tests). */
|
|
195
214
|
ghComment?: (prNumber: number, body: string) => void;
|
|
215
|
+
/** Read-only host gh lookup; injected fixtures can stand in for gh without creating or closing a PR. */
|
|
216
|
+
lookupOpenPrByBranch?: (branch: string, repoUrl: string) => Promise<OpenPrByBranch> | OpenPrByBranch;
|
|
196
217
|
draftResidue?: (record: PrTerminalRecord, ctx: DraftResidueContext) => Promise<void> | void;
|
|
197
218
|
/** 🔑 Pod 필수 스킬의 키(.env)를 이 런의 Secret 으로 넘긴다 — 명시 opt-in(유료 크레딧을 쓴다). */
|
|
198
219
|
skillEnv?: boolean;
|
|
@@ -385,6 +406,11 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
|
|
|
385
406
|
// ⑴ 잠금을 기다리는 사이 동료 런이 갱신했으면 그대로 쓴다 — 두 번째 갱신이 회전된 토큰으로 실패하는 자리.
|
|
386
407
|
const first = reread();
|
|
387
408
|
if (!('expiringH' in first)) { observe('refresh-skipped-already-fresh', null); return first; }
|
|
409
|
+
// The lease can transfer while an account lock is queued. Recheck inside the
|
|
410
|
+
// critical section, immediately before the only refresh-token operation.
|
|
411
|
+
let stillHolder = false;
|
|
412
|
+
try { stillHolder = await deps.isLeaseHolder(); } catch { stillHolder = false; }
|
|
413
|
+
if (!stillHolder) { observe('no-lease', null, 'lease-lost-before-refresh'); return null; }
|
|
388
414
|
let refreshed: Awaited<ReturnType<typeof deps.refresh>> | null = null;
|
|
389
415
|
let failReason = 'threw';
|
|
390
416
|
try { refreshed = await deps.refresh(account); }
|
|
@@ -418,12 +444,13 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
|
|
|
418
444
|
|
|
419
445
|
/** 기본 임대 판정 — 기존 HQ 펜스(`ledger-cli` 역할 · fail-open 없음)를 그대로 쓴다. */
|
|
420
446
|
async function defaultHqLeaseHolder(): Promise<boolean> {
|
|
447
|
+
if (process.env.ELANOUS_REMOTE_CODEX_ACCESS_ONLY === '1') return false;
|
|
421
448
|
const { hqFenceDecision } = await import('../../hq/hq.js');
|
|
422
449
|
return hqFenceDecision('ledger-cli').run;
|
|
423
450
|
}
|
|
424
451
|
|
|
425
452
|
/** 호스트 계정 → refresh 없는 사본(elanous 저장소 ⊕ codex auth.json) ⊕ gh 토큰. */
|
|
426
|
-
export function hostCredentials(account: string, storePath: string = authStorePath(), ghToken: () => string = defaultGhToken): { elanousAuth: string; codexAuth: string; ghToken: string } {
|
|
453
|
+
export function hostCredentials(account: string, storePath: string = authStorePath(), ghToken: () => string = defaultGhToken, options: { requireExplicitChatgptAuthMode?: boolean } = {}): { elanousAuth: string; codexAuth: string; ghToken: string } {
|
|
427
454
|
// 경로는 해석기로(격리 게이트 · 2026-09-25) — 손으로 `~/.elanous/auth.json` 을 조립하면 test↔prod 격리가 새는 자리가 된다.
|
|
428
455
|
const store = JSON.parse(readFileSync(storePath, 'utf8')) as { version?: number; providers: Record<string, Record<string, unknown>> };
|
|
429
456
|
// ⭐ 기본 계정은 정본 키가 `openai-codex`(이름 없음)이고 홈을 안 적는다 — 기본 위치로 푼다(회전 후보와 같은 해석).
|
|
@@ -431,22 +458,70 @@ export function hostCredentials(account: string, storePath: string = authStorePa
|
|
|
431
458
|
const entry = store.providers[storeKey] as { tokens: Record<string, unknown>; lastRefresh?: string; authMode?: string; codexHome?: string } | undefined;
|
|
432
459
|
const codexHome = entry?.codexHome ?? (entry && account === 'default' ? resolveCodexAccount(envLiteral({ CODEX_HOME: process.env.CODEX_HOME })).home : undefined);
|
|
433
460
|
if (!entry || !codexHome) throw new Error(`${storeKey} 계정이 없거나 codexHome 을 모른다`);
|
|
434
|
-
const codex = JSON.parse(readFileSync(join(codexHome, 'auth.json'), 'utf8')) as { tokens: Record<string, unknown> };
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
461
|
+
const codex = JSON.parse(readFileSync(join(codexHome, 'auth.json'), 'utf8')) as { auth_mode?: string; tokens: Record<string, unknown> };
|
|
462
|
+
// Remote parents must not infer this from store metadata; ordinary Pod callers retain the legacy fallback.
|
|
463
|
+
if (options.requireExplicitChatgptAuthMode && codex.auth_mode !== 'chatgpt') throw new Error(`${storeKey} Codex CLI auth_mode must be explicitly chatgpt for remote parent`);
|
|
464
|
+
const mirror = createCodexAccessMirror({ account, store, codexAuth: codex });
|
|
465
|
+
const hoursLeft = (mirror.expiresAt - Date.now()) / 3_600_000;
|
|
466
|
+
if (hoursLeft < 3) throw new PodCodexExpiringError(account, Math.round(hoursLeft * 10) / 10);
|
|
467
|
+
// ⭐ P4(2026-09-26): both stores use the just-checked Codex access token; the stores refresh independently.
|
|
468
|
+
return { elanousAuth: mirror.elanousAuth, codexAuth: mirror.codexAuth, ghToken: ghToken() };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** POD-GROK-VALIDITY-FIT (10-17) — the Pod gets a refresh-less grok access copy, so the copy must outlive the Job.
|
|
472
|
+
* It used to require 3 h (= the full POD_JOB_DEADLINE_SECONDS), but the token lives only 6 h and the grok CLI renews it
|
|
473
|
+
* only right before expiry (`grok models`/`grok -p` leave `expires_at` unchanged — node-b refreshOutcome:"unchanged"), and
|
|
474
|
+
* the CLI has no forced-refresh command (`grok --help`/`grok login --help` 10-17: login·logout only). So half of every
|
|
475
|
+
* day refused grok Pod launches (10-17 node-b·node-c·mbp 9 no-launch · 3 hand re-logins).
|
|
476
|
+
* ⇒ The Job is fitted to the token instead: a launch is allowed when the token has at least a minimum Job
|
|
477
|
+
* (POD_GROK_MIN_JOB_SECONDS) plus a margin left, and that launch's `activeDeadlineSeconds` is cut to the token's
|
|
478
|
+
* remaining life minus the margin (`fitPodDeadlineToGrok`) — so a long run ends as a classified DeadlineExceeded
|
|
479
|
+
* instead of a silent mid-run 401. With the host credential relay (pod.groundingUrl) the Pod renews mid-run and
|
|
480
|
+
* nothing is cut. 90 min = the Job cap before 대표 09-26 raised it (only two runs had reached it). */
|
|
481
|
+
export const POD_GROK_MIN_JOB_SECONDS = 90 * 60;
|
|
482
|
+
/** Slack between the fitted Job end and the token expiry (clock skew · Pod scheduling). */
|
|
483
|
+
export const POD_GROK_EXPIRY_MARGIN_MS = 15 * 60_000;
|
|
484
|
+
/** Minimum remaining token life for a grok Pod launch = minimum Job + margin (1 h 45 m). */
|
|
485
|
+
export const POD_GROK_MIN_VALIDITY_MS = POD_GROK_MIN_JOB_SECONDS * 1000 + POD_GROK_EXPIRY_MARGIN_MS;
|
|
486
|
+
|
|
487
|
+
/** «1h45m» · «20m» · «만료» — remaining-time text for refusal lines (no token value). */
|
|
488
|
+
export function formatGrokRemaining(ms: number | null): string {
|
|
489
|
+
if (ms === null || !Number.isFinite(ms)) return '알 수 없음';
|
|
490
|
+
if (ms <= 0) return '만료됨';
|
|
491
|
+
const m = Math.floor(ms / 60_000);
|
|
492
|
+
return m >= 60 ? `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}m` : `${m}m`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Latest `expires_at` among scopes that carry an access key (the token the Pod would get). */
|
|
496
|
+
function grokSubscriptionExpiresAtMs(home?: string): number | null {
|
|
497
|
+
try {
|
|
498
|
+
const scopes = JSON.parse(readFileSync(grokAuthFilePath(home), 'utf8')) as Record<string, { key?: unknown; expires_at?: unknown }>;
|
|
499
|
+
const all = Object.values(scopes).flatMap((s) => (s && typeof s.key === 'string' && s.key && typeof s.expires_at === 'string' && Number.isFinite(Date.parse(s.expires_at)) ? [Date.parse(s.expires_at)] : []));
|
|
500
|
+
return all.length ? Math.max(...all) : null;
|
|
501
|
+
} catch { return null; }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** POD-GROK-VALIDITY-FIT — this launch's Job deadline for a refresh-less grok subscription copy.
|
|
505
|
+
* · relay (the Pod can renew from the host) or unknown expiry → the requested deadline as is.
|
|
506
|
+
* · otherwise → min(requested, remaining − margin); below the minimum Job (time passed since the credential check)
|
|
507
|
+
* the launch is refused — the deadline never outlives the token. */
|
|
508
|
+
export function fitPodDeadlineToGrok(o: { requestedSeconds: number; expiresAtMs: number | null; nowMs: number; relay: boolean }):
|
|
509
|
+
{ refused: false; deadlineSeconds: number; fitted: boolean; availableSeconds: number | null } | { refused: true; availableSeconds: number } {
|
|
510
|
+
if (o.relay || o.expiresAtMs === null || !Number.isFinite(o.expiresAtMs)) return { refused: false, deadlineSeconds: o.requestedSeconds, fitted: false, availableSeconds: null };
|
|
511
|
+
const availableSeconds = Math.floor((o.expiresAtMs - o.nowMs - POD_GROK_EXPIRY_MARGIN_MS) / 1000);
|
|
512
|
+
if (availableSeconds < POD_GROK_MIN_JOB_SECONDS) return { refused: true, availableSeconds };
|
|
513
|
+
if (availableSeconds >= o.requestedSeconds) return { refused: false, deadlineSeconds: o.requestedSeconds, fitted: false, availableSeconds };
|
|
514
|
+
return { refused: false, deadlineSeconds: availableSeconds, fitted: true, availableSeconds };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Expiry of the access the Pod will actually use — only scopes with a key, latest wins (same rule as the Pod reader). */
|
|
518
|
+
export function shippedGrokExpiresAtMs(grokAuth: string): number | null {
|
|
519
|
+
try {
|
|
520
|
+
const scopes = JSON.parse(grokAuth) as Record<string, { key?: unknown; expires_at?: unknown }>;
|
|
521
|
+
const all = Object.values(scopes).flatMap((sc) => (sc && typeof sc.key === 'string' && sc.key && typeof sc.expires_at === 'string' && Number.isFinite(Date.parse(sc.expires_at)) ? [Date.parse(sc.expires_at)] : []));
|
|
522
|
+
return all.length ? Math.max(...all) : null;
|
|
523
|
+
} catch { return null; }
|
|
446
524
|
}
|
|
447
|
-
|
|
448
|
-
/** A Pod gets a refresh-less access copy, so the copy must outlive the Job: same rule as the Codex path (3 h). */
|
|
449
|
-
export const POD_GROK_MIN_VALIDITY_MS = 3 * 3600_000;
|
|
450
525
|
/** PODCRED1 (10-05): an App installation token lives ~60 min and the host renews it in the Pod at the ten-minute
|
|
451
526
|
* boundary; below this remaining life at launch the token is observed (`github-app-short-lived`). */
|
|
452
527
|
export const POD_APP_TOKEN_MIN_START_MS = 20 * 60_000;
|
|
@@ -466,13 +541,21 @@ export function hostGrokCredentials(opts: {
|
|
|
466
541
|
home?: string; env?: NodeJS.ProcessEnv; apiKeyOptIn?: boolean; ghToken?: () => string;
|
|
467
542
|
/** Test seams — the host refresh (read-only `grok models`) and the expiry probe. */
|
|
468
543
|
refresh?: () => void; isExpiring?: () => boolean;
|
|
544
|
+
/** Required remaining token life (default POD_GROK_MIN_VALIDITY_MS) — a caller whose Job is not fitted passes its own deadline. */
|
|
545
|
+
minValidityMs?: number; now?: () => number;
|
|
469
546
|
} = {}): { grokAuth?: string; grokApiKey?: string; ghToken: string } {
|
|
470
547
|
let credential = resolveGrokCredential({ home: opts.home, env: opts.env });
|
|
471
548
|
if (credential?.kind === 'subscription') {
|
|
472
|
-
const
|
|
549
|
+
const minValidityMs = opts.minValidityMs ?? POD_GROK_MIN_VALIDITY_MS;
|
|
550
|
+
const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: minValidityMs, ...(opts.now ? { now: opts.now } : {}) }));
|
|
473
551
|
if (expiring()) {
|
|
474
552
|
try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { /* judged below */ }
|
|
475
|
-
if (expiring())
|
|
553
|
+
if (expiring()) {
|
|
554
|
+
const at = grokSubscriptionExpiresAtMs(opts.home);
|
|
555
|
+
const remainingMs = at === null ? null : at - (opts.now ?? Date.now)();
|
|
556
|
+
try { debug.log('pod.grok-credentials', 'refused', { remainingMs, minValidityMs, expiresAt: at === null ? null : new Date(at).toISOString() }); } catch { /* fail-soft */ }
|
|
557
|
+
throw new Error(`grok: 구독 access 토큰 남은 시간 ${formatGrokRemaining(remainingMs)} < Pod 발사 기준 ${formatGrokRemaining(minValidityMs)} — 호스트 갱신(grok models)도 못 늘렸다 · 다음: 호스트에서 grok login --device-auth 후 다시`);
|
|
558
|
+
}
|
|
476
559
|
credential = resolveGrokCredential({ home: opts.home, env: opts.env });
|
|
477
560
|
if (credential?.kind !== 'subscription') throw new Error('grok: 갱신 뒤 구독 자격을 다시 읽지 못했다');
|
|
478
561
|
}
|
|
@@ -554,9 +637,12 @@ export function grokLaunchReadiness(opts: { home?: string; env?: NodeJS.ProcessE
|
|
|
554
637
|
}
|
|
555
638
|
|
|
556
639
|
/** The one launch line for a skipped grok copy — the reader sees why grok left this run's chain. */
|
|
557
|
-
export function podGrokSkippedLine(skip: { reason: PodGrokSkipReason; expiresAt: string | null }): string {
|
|
640
|
+
export function podGrokSkippedLine(skip: { reason: PodGrokSkipReason; expiresAt: string | null }, now?: () => number): string {
|
|
558
641
|
const why = skip.reason === 'missing' ? '구독 자격 없음' : skip.reason === 'expiring' ? '만료 임박' : '만료 임박 · 호스트 갱신 실패';
|
|
559
|
-
|
|
642
|
+
const at = skip.expiresAt ? Date.parse(skip.expiresAt) : NaN;
|
|
643
|
+
const left = skip.reason !== 'missing' && Number.isFinite(at) ? ` · 남은 ${formatGrokRemaining(at - (now ?? Date.now)())} < 기준 ${formatGrokRemaining(POD_GROK_MIN_VALIDITY_MS)}` : '';
|
|
644
|
+
const next = skip.reason === 'missing' ? '' : ' · 다음: 호스트에서 grok login --device-auth';
|
|
645
|
+
return `⚠️ grok 자격 없음(${why}${skip.expiresAt ? ` · 만료 ${skip.expiresAt}` : ''}${left}) — 이 런의 폴백 체인에서 grok 을 뺐다${next}`;
|
|
560
646
|
}
|
|
561
647
|
|
|
562
648
|
/** 키 캐시 관례: `~/.cache/<env 이름 소문자>`(예: OPENROUTER_API_KEY → ~/.cache/openrouter_api_key · src/config.ts 와 같다). */
|
|
@@ -578,20 +664,45 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
|
|
|
578
664
|
return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
|
|
579
665
|
}
|
|
580
666
|
|
|
581
|
-
/** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403)
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
667
|
+
/** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403).
|
|
668
|
+
* The global hook refuses main/tags/unknown refs and most deletes. With a runId, self-impl refs use the same six-hex
|
|
669
|
+
* run suffix as planned branch names; only that suffix and this run's disposable PR scratch create/delete pass.
|
|
670
|
+
* A same-card PR reuse push is the one exception: per-command Git config binds its ref, old head and card;
|
|
671
|
+
* the hook compares the Pod card and proves fast-forward ancestry before accepting it.
|
|
672
|
+
* salvage/* and bench arm refs keep their existing broader rules. A six-hex collision remains possible, so this is
|
|
673
|
+
* an accidental overwrite guard, not a security isolation boundary; child credential isolation is a separate slice. */
|
|
586
674
|
export const POD_GIT_HOOKS_DIR = '$HOME/.elanous-git-hooks';
|
|
587
|
-
export function podGitPushGuardScript(): string {
|
|
675
|
+
export function podGitPushGuardScript(runId?: string): string {
|
|
676
|
+
const runKey = runId ? runSuffixKey(runId) : null;
|
|
677
|
+
const zeroSha = '0'.repeat(40);
|
|
588
678
|
return [
|
|
589
679
|
`mkdir -p "${POD_GIT_HOOKS_DIR}" && cat > "${POD_GIT_HOOKS_DIR}/pre-push" <<'ELANOUS_PRE_PUSH'`,
|
|
590
680
|
'#!/bin/sh',
|
|
591
681
|
'while read -r lref lsha rref rsha; do',
|
|
592
|
-
' case "$lsha" in *[!0]*) ;; *)
|
|
682
|
+
' case "$lsha" in *[!0]*) ;; *)',
|
|
683
|
+
...(runKey ? [
|
|
684
|
+
` case "$rref" in refs/heads/self-impl/pr-create-r${runKey}-*) ;; *) echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;; esac ;;`,
|
|
685
|
+
] : [' echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;;']),
|
|
686
|
+
' esac',
|
|
593
687
|
' case "$rref" in',
|
|
594
|
-
|
|
688
|
+
...(runKey ? [
|
|
689
|
+
` refs/heads/self-impl/pr-create-r${runKey}-*)`,
|
|
690
|
+
` case "$lsha:$rsha" in ${zeroSha}:*|*:${zeroSha}) ;; *) echo "[pod] git-guard: refused scratch ref update $rref" >&2; exit 1 ;; esac ;;`,
|
|
691
|
+
` refs/heads/self-impl/*-r${runKey}|refs/heads/self-impl/*-r${runKey}-early) ;;`,
|
|
692
|
+
' refs/heads/self-impl/*)',
|
|
693
|
+
' reuse_ref=$(git config --get elanous.sameCellReuseRef 2>/dev/null || true)',
|
|
694
|
+
' reuse_head=$(git config --get elanous.sameCellReuseHead 2>/dev/null || true)',
|
|
695
|
+
' reuse_card=$(git config --get elanous.sameCellReuseCard 2>/dev/null || true)',
|
|
696
|
+
' if [ -z "$reuse_ref" ]; then echo "[pod] git-guard: refused push to $rref (another run)" >&2; exit 1; fi',
|
|
697
|
+
' if [ "$rref" != "$reuse_ref" ]; then echo "[pod] git-guard: refused same-cell ref-mismatch $rref" >&2; exit 1; fi',
|
|
698
|
+
' 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',
|
|
699
|
+
' if [ -z "$reuse_head" ] || [ "$rsha" != "$reuse_head" ]; then echo "[pod] git-guard: refused same-cell remote-head-mismatch $rref" >&2; exit 1; fi',
|
|
700
|
+
' 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',
|
|
701
|
+
' echo "[pod] git-guard: allowed same-cell fast-forward $rref" >&2 ;;',
|
|
702
|
+
] : [
|
|
703
|
+
...(runId ? [' refs/heads/self-impl/*) echo "[pod] git-guard: refused push to $rref (invalid run id)" >&2; exit 1 ;;'] : [' refs/heads/self-impl/*) ;;']),
|
|
704
|
+
]),
|
|
705
|
+
' refs/heads/salvage/*) ;;',
|
|
595
706
|
' refs/heads/bench/*/base) echo "[pod] git-guard: refused push to shared bench base $rref" >&2; exit 1 ;;',
|
|
596
707
|
' refs/heads/bench/*/*) ;;',
|
|
597
708
|
' *) echo "[pod] git-guard: refused push to $rref (only this run\'s branches)" >&2; exit 1 ;;',
|
|
@@ -769,7 +880,7 @@ export const POD_ATTEMPT_ANNOTATION = 'elanous.dev/attempt';
|
|
|
769
880
|
export function podRunLabels(o: { runId?: string; parentRunId?: string }): Record<string, string> {
|
|
770
881
|
const run = k8sLabelValue(o.parentRunId ?? o.runId);
|
|
771
882
|
const child = k8sLabelValue(o.runId);
|
|
772
|
-
return { ...(run ? { 'elanous.run': run } : {}), ...(child
|
|
883
|
+
return { ...(run ? { 'elanous.run': run } : {}), ...(child ? { 'elanous.child-run': child } : {}) };
|
|
773
884
|
}
|
|
774
885
|
|
|
775
886
|
/** Pod 메모리 «등급» — 대표 09-27: PWA Pod 골이 16Gi 에서 OOM 으로 죽는다 ⇒ «high» 등급을 옵션으로 고른다.
|
|
@@ -892,7 +1003,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
|
|
|
892
1003
|
}
|
|
893
1004
|
|
|
894
1005
|
/** A named child provider decides the Pod provider; paid OpenRouter requires an explicit key and model. */
|
|
895
|
-
export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
|
|
1006
|
+
export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; openGatewayKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
|
|
896
1007
|
// 공백·빈 이름 = «미지정»(종전 기본 경로) — 유료 provider 로 흘러가지 않는다(codex 재리뷰 must-fix).
|
|
897
1008
|
if (!named?.trim()) return {};
|
|
898
1009
|
// ⭐ 목록은 한 곳(pod-child-providers.ts) — 발사 관문도 같은 상수·같은 문면으로 거부한다.
|
|
@@ -907,6 +1018,12 @@ export function podNamedChildProvider(named: string | undefined, credential: { g
|
|
|
907
1018
|
if (!childModel?.trim()) return { refuse: 'pod: openrouter 자식 모델 없음 — --child-llm-model 필요' };
|
|
908
1019
|
return { provider: 'openrouter' };
|
|
909
1020
|
}
|
|
1021
|
+
if (named === 'opengateway') {
|
|
1022
|
+
if (!credential.openGatewayKey) return { refuse: 'pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요' };
|
|
1023
|
+
if (!childModel?.trim()) return { refuse: 'pod: opengateway 자식 모델 없음 — --child-llm-model 필요' };
|
|
1024
|
+
if (!isVerifiedOpenGatewayModel(childModel.trim())) return { refuse: `pod: opengateway model not verified: ${childModel.trim()}` };
|
|
1025
|
+
return { provider: 'opengateway' };
|
|
1026
|
+
}
|
|
910
1027
|
// BEDROCK-PROVIDER ② — 자격은 클러스터의 이름 있는 Secret 에서 온다. 있나·키가 갖춰졌나는 Pod 발사 «전»(apply 0)에 판정한다(checkBedrockPodSecret).
|
|
911
1028
|
if (named === 'bedrock') return { provider: 'bedrock' };
|
|
912
1029
|
// ZAI-POD-CHILD — glm 도 같은 결: 자격은 클러스터의 이름 있는 ZAI Secret · 있나·동시 상한은 Pod 발사 «전»(apply 0)에 판정한다(pod-zai-child.ts).
|
|
@@ -941,7 +1058,8 @@ export function podChildLlmArgs(options: Pick<PodSpawnOptions, 'provider' | 'chi
|
|
|
941
1058
|
const provider = options.provider === 'openai-codex' && !options.childProviderExplicit ? undefined : options.provider;
|
|
942
1059
|
if (!provider) return [];
|
|
943
1060
|
const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : provider === 'glm' ? ZAI_CODING_DEFAULT_MODEL : undefined);
|
|
944
|
-
if (provider === 'openrouter' && !model) throw new Error(
|
|
1061
|
+
if ((provider === 'openrouter' || provider === 'opengateway') && !model) throw new Error(`pod: ${provider} 자식 모델 없음 — --child-llm-model 필요`);
|
|
1062
|
+
if (provider === 'opengateway' && !isVerifiedOpenGatewayModel(model!)) throw new Error(`pod: opengateway model not verified: ${model}`);
|
|
945
1063
|
// `self implement` registers no --child-llm-effort — passing it kills the Pod at argument parsing, so effort stays host-only.
|
|
946
1064
|
return ['--child-llm-provider', provider, ...(model ? ['--child-llm-model', model] : [])];
|
|
947
1065
|
}
|
|
@@ -998,7 +1116,7 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
|
|
|
998
1116
|
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`;
|
|
999
1117
|
}
|
|
1000
1118
|
|
|
1001
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 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; 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> {
|
|
1119
|
+
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> {
|
|
1002
1120
|
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'))) {
|
|
1003
1121
|
throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
|
|
1004
1122
|
}
|
|
@@ -1007,6 +1125,8 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1007
1125
|
const childModelIndex = o.args.indexOf('--child-llm-model');
|
|
1008
1126
|
const openrouterModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'openrouter' && childModelIndex >= 0
|
|
1009
1127
|
? o.args[childModelIndex + 1] : undefined;
|
|
1128
|
+
const openGatewayModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'opengateway' && childModelIndex >= 0
|
|
1129
|
+
? o.args[childModelIndex + 1] : undefined;
|
|
1010
1130
|
// POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
|
|
1011
1131
|
const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
|
|
1012
1132
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
@@ -1030,7 +1150,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1030
1150
|
'mkdir -p "$HOME/.elanous" && install -m 600 /creds/llm-fallback-config.json "$HOME/.elanous/config.json" || exit 6',
|
|
1031
1151
|
`echo "ELANOUS_POD_FALLBACK_CHAIN source=${o.fallbackChain.source} steps=${o.fallbackChain.steps.join(',')}"`,
|
|
1032
1152
|
] : []),
|
|
1033
|
-
|
|
1153
|
+
openGatewayModel
|
|
1154
|
+
? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=opengateway'
|
|
1155
|
+
: openrouterModel
|
|
1034
1156
|
? o.reviewerCodexAuth
|
|
1035
1157
|
? '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'
|
|
1036
1158
|
: 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=openrouter'
|
|
@@ -1049,7 +1171,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1049
1171
|
// 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
|
|
1050
1172
|
...(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(',')}"`] : []),
|
|
1051
1173
|
'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
|
|
1052
|
-
podGitPushGuardScript(),
|
|
1174
|
+
podGitPushGuardScript(o.runId),
|
|
1053
1175
|
'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
|
|
1054
1176
|
// SI-POD-STAGE-MARKERS — Pod 시계 단계 표지(`ELANOUS_POD_STAGE <stage> <start|end> <epoch초>`): 호스트가 Job 로그에서 읽어
|
|
1055
1177
|
// pod.lifecycle stages 의 clone · mirror · run 을 채운다. 실패한 단계는 끝 표지가 없다(→ missing · 지어내지 않는다).
|
|
@@ -1057,6 +1179,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1057
1179
|
siTaskPodStageShell('clone', 'start'),
|
|
1058
1180
|
podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl, undefined, { stageMarkers: true }),
|
|
1059
1181
|
siTaskPodStageShell('clone', 'end'),
|
|
1182
|
+
// This image already contains runtime dependencies; there is no child install command.
|
|
1183
|
+
// Record that fact before the main command, without treating clone completion as install completion.
|
|
1184
|
+
`printf 'ELANOUS_POD_DEPS skipped %s image-prepared-no-install\\n' "$(date +%s)" || true`,
|
|
1060
1185
|
...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
|
|
1061
1186
|
...(goalPath ? [
|
|
1062
1187
|
`mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
|
|
@@ -1151,7 +1276,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1151
1276
|
return {
|
|
1152
1277
|
apiVersion: 'batch/v1',
|
|
1153
1278
|
kind: 'Job',
|
|
1154
|
-
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
|
|
1279
|
+
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' } : {}) },
|
|
1155
1280
|
...((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' } : {}) } } : {}) },
|
|
1156
1281
|
spec: {
|
|
1157
1282
|
backoffLimit: 0,
|
|
@@ -1159,7 +1284,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1159
1284
|
activeDeadlineSeconds: o.deadlineSeconds,
|
|
1160
1285
|
template: {
|
|
1161
1286
|
// ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
|
|
1162
|
-
metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
|
|
1287
|
+
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' } : {}) } },
|
|
1163
1288
|
spec: {
|
|
1164
1289
|
restartPolicy: 'Never',
|
|
1165
1290
|
securityContext: { runAsUser: 1000, fsGroup: 1000 },
|
|
@@ -1175,6 +1300,8 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1175
1300
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
1176
1301
|
...(o.parentRunId ? [{ name: 'ELANOUS_PARENT_RUN_ID', value: o.parentRunId }] : []),
|
|
1177
1302
|
...(o.workIdentity?.taskCard ? [{ name: 'ELANOUS_TASK_CARD_ID', value: o.workIdentity.taskCard }] : []),
|
|
1303
|
+
...(o.boundPrNumber && Number.isSafeInteger(o.boundPrNumber) && o.boundPrNumber > 0
|
|
1304
|
+
? [{ name: 'ELANOUS_TASK_BOUND_PR', value: String(o.boundPrNumber) }] : []),
|
|
1178
1305
|
...(o.workIdentity?.slotId ? [{ name: 'ELANOUS_POD_SLOT_ID', value: o.workIdentity.slotId }] : []),
|
|
1179
1306
|
{ name: 'ELANOUS_SUBSTRATE', value: 'pod' },
|
|
1180
1307
|
...Object.entries(controlInboxEnv(POD_CONTROL_INBOX_DIR)).map(([name, value]) => ({ name, value })),
|
|
@@ -1189,7 +1316,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1189
1316
|
...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
|
|
1190
1317
|
...o.passEnv.map((key) => ({ name: key, valueFrom: { secretKeyRef: { name: `${o.name}-creds`, key: `env-${key}` } } })),
|
|
1191
1318
|
...(o.bedrock ? bedrockPodEnv(o.bedrock) : []),
|
|
1192
|
-
...(o.zai ? zaiPodEnv(o.zai) : []),
|
|
1319
|
+
...(o.zai || o.zaiFallback ? zaiPodEnv(o.zai ?? o.zaiFallback!) : []),
|
|
1193
1320
|
],
|
|
1194
1321
|
volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }, ...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : [])],
|
|
1195
1322
|
}],
|
|
@@ -1215,6 +1342,85 @@ function defaultGhComment(prNumber: number, body: string): void {
|
|
|
1215
1342
|
if (r.status !== 0) throw new Error((r.stderr || r.error?.message || `gh exited ${r.status}`).trim().slice(0, 200));
|
|
1216
1343
|
}
|
|
1217
1344
|
|
|
1345
|
+
function defaultLookupOpenPrByBranch(branch: string, repoUrl: string): OpenPrByBranch {
|
|
1346
|
+
const repo = /^https:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/u.exec(repoUrl)?.[1];
|
|
1347
|
+
if (!repo) return { status: 'failed', reason: 'repository-unrecognized' };
|
|
1348
|
+
if (!branch || !/^[A-Za-z0-9._/-]+$/u.test(branch) || branch.includes('..')) return { status: 'failed', reason: 'branch-invalid' };
|
|
1349
|
+
const result = spawnSync('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--head', branch,
|
|
1350
|
+
'--json', 'number,url,headRefName,isCrossRepository', '--limit', '100'], { encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576 });
|
|
1351
|
+
if (result.status !== 0) return { status: 'failed', reason: result.error ? 'gh-list-unavailable' : `gh-list-rc-${result.status ?? 'null'}` };
|
|
1352
|
+
let rows: unknown;
|
|
1353
|
+
try { rows = JSON.parse(result.stdout); } catch { return { status: 'failed', reason: 'gh-json-invalid' }; }
|
|
1354
|
+
if (!Array.isArray(rows)) return { status: 'failed', reason: 'gh-json-invalid' };
|
|
1355
|
+
if (rows.some((row) => row === null || typeof row !== 'object'
|
|
1356
|
+
|| typeof (row as { number?: unknown }).number !== 'number'
|
|
1357
|
+
|| !Number.isSafeInteger((row as { number: number }).number)
|
|
1358
|
+
|| typeof (row as { url?: unknown }).url !== 'string'
|
|
1359
|
+
|| typeof (row as { headRefName?: unknown }).headRefName !== 'string'
|
|
1360
|
+
|| typeof (row as { isCrossRepository?: unknown }).isCrossRepository !== 'boolean'))
|
|
1361
|
+
return { status: 'failed', reason: 'gh-json-invalid' };
|
|
1362
|
+
if (rows.length >= 100) return { status: 'failed', reason: 'gh-result-limit-reached' };
|
|
1363
|
+
const matching = rows.filter((row): row is { number: number; url: string; headRefName: string; isCrossRepository: false } => {
|
|
1364
|
+
if (row === null || typeof row !== 'object') return false;
|
|
1365
|
+
const candidate = row as { number?: unknown; url?: unknown; headRefName?: unknown; isCrossRepository?: unknown };
|
|
1366
|
+
return candidate.headRefName === branch && candidate.isCrossRepository === false && typeof candidate.number === 'number'
|
|
1367
|
+
&& Number.isSafeInteger(candidate.number) && candidate.number > 0 && typeof candidate.url === 'string';
|
|
1368
|
+
});
|
|
1369
|
+
if (matching.length === 0) return { status: 'not-found', reason: rows.some((row) =>
|
|
1370
|
+
(row as { headRefName: string }).headRefName === branch && (row as { isCrossRepository: boolean }).isCrossRepository)
|
|
1371
|
+
? 'cross-repository-only' : 'no-open-pr-for-branch' };
|
|
1372
|
+
if (matching.length !== 1) return { status: 'failed', reason: 'ambiguous-open-pr-for-branch' };
|
|
1373
|
+
const row = matching[0]!;
|
|
1374
|
+
if (row.url.replace(/\/$/u, '').toLowerCase() !== `https://github.com/${repo}/pull/${row.number}`.toLowerCase())
|
|
1375
|
+
return { status: 'failed', reason: 'pr-url-mismatch' };
|
|
1376
|
+
return { status: 'found', prNumber: row.number, prUrl: row.url };
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
type PodImageObservation = { podName: string | null; podUid: string | null; podCreatedAt: string | null;
|
|
1380
|
+
imageID: string | null; digest: string | null; digestUnavailable: string | null };
|
|
1381
|
+
|
|
1382
|
+
/** Read the newest Pod owned by this Job. A stale replacement must never be named as the final Pod. */
|
|
1383
|
+
function readJobPodImage(kubectl: Kubectl, namespace: string, job: string, jobUid?: string): PodImageObservation {
|
|
1384
|
+
const unavailable = (reason: string, podName: string | null = null, imageID: string | null = null,
|
|
1385
|
+
podUid: string | null = null, podCreatedAt: string | null = null): PodImageObservation =>
|
|
1386
|
+
({ podName, podUid, podCreatedAt, imageID, digest: null, digestUnavailable: reason });
|
|
1387
|
+
let result: ReturnType<Kubectl>;
|
|
1388
|
+
try { result = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'json', '--request-timeout=5s']); }
|
|
1389
|
+
catch { return unavailable('pod-status-threw'); }
|
|
1390
|
+
if (result.status !== 0) return unavailable(`pod-status-rc-${result.status ?? 'null'}`);
|
|
1391
|
+
let rows: unknown;
|
|
1392
|
+
try { rows = (JSON.parse(result.stdout) as { items?: unknown }).items; }
|
|
1393
|
+
catch { return unavailable('pod-status-invalid-json'); }
|
|
1394
|
+
if (!Array.isArray(rows)) return unavailable('pod-status-invalid-items');
|
|
1395
|
+
const owned = rows.filter((row): row is {
|
|
1396
|
+
metadata: { name: string; uid?: string; creationTimestamp?: string; ownerReferences: Array<{ kind: string; name: string; uid?: string }> };
|
|
1397
|
+
status?: { containerStatuses?: Array<{ name?: string; imageID?: string }> };
|
|
1398
|
+
} => {
|
|
1399
|
+
if (!row || typeof row !== 'object') return false;
|
|
1400
|
+
const meta = (row as { metadata?: unknown }).metadata;
|
|
1401
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
1402
|
+
const m = meta as { name?: unknown; ownerReferences?: unknown };
|
|
1403
|
+
if (typeof m.name !== 'string' || !Array.isArray(m.ownerReferences)) return false;
|
|
1404
|
+
return m.ownerReferences.some((owner: unknown) => !!owner && typeof owner === 'object'
|
|
1405
|
+
&& (owner as { kind?: unknown }).kind === 'Job' && (owner as { name?: unknown }).name === job
|
|
1406
|
+
&& (!jobUid || (owner as { uid?: unknown }).uid === jobUid));
|
|
1407
|
+
}).sort((a, b) => {
|
|
1408
|
+
const at = Date.parse(a.metadata.creationTimestamp ?? '') || 0;
|
|
1409
|
+
const bt = Date.parse(b.metadata.creationTimestamp ?? '') || 0;
|
|
1410
|
+
return bt - at || b.metadata.name.localeCompare(a.metadata.name);
|
|
1411
|
+
});
|
|
1412
|
+
const pod = owned[0];
|
|
1413
|
+
if (!pod) return unavailable(rows.length ? 'no-job-owned-pod' : 'pod-not-created');
|
|
1414
|
+
const podUid = typeof pod.metadata.uid === 'string' ? pod.metadata.uid : null;
|
|
1415
|
+
const podCreatedAt = typeof pod.metadata.creationTimestamp === 'string' ? pod.metadata.creationTimestamp : null;
|
|
1416
|
+
const child = pod.status?.containerStatuses?.find((entry) => entry.name === 'child');
|
|
1417
|
+
if (!child) return unavailable('child-container-not-started', pod.metadata.name, null, podUid, podCreatedAt);
|
|
1418
|
+
const imageID = typeof child.imageID === 'string' ? child.imageID : null;
|
|
1419
|
+
if (!imageID?.trim()) return unavailable('image-id-missing', pod.metadata.name, imageID, podUid, podCreatedAt);
|
|
1420
|
+
const digest = /(?:^|[@/])(sha256:[0-9a-f]{64})$/iu.exec(imageID.trim())?.[1]?.toLowerCase() ?? null;
|
|
1421
|
+
return { podName: pod.metadata.name, podUid, podCreatedAt, imageID, digest, digestUnavailable: digest ? null : 'nonstandard-image-id' };
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1218
1424
|
export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
|
|
1219
1425
|
if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
|
|
1220
1426
|
throw new Error('repair commit-only requires a matching PR source and exact head');
|
|
@@ -1342,9 +1548,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1342
1548
|
const timer = setTimeout(() => {
|
|
1343
1549
|
if (stalledStages.has(stage)) return;
|
|
1344
1550
|
stalledStages.add(stage);
|
|
1345
|
-
const pool = options.pool as { waitReason?: () => string | null } | undefined;
|
|
1551
|
+
const pool = options.pool as { waitReason?: (memoryLimit?: string) => string | null } | undefined;
|
|
1346
1552
|
debug.log('harness.launch', 'launch-stalled', { spaceId: input.spaceId, runId: childRunId, stage, waitedSec: Math.round((Date.now() - started) / 1000),
|
|
1347
|
-
reason: (stage === 'pool-slot' ? stageReason['pool-slot'] : (typeof pool?.waitReason === 'function' ? pool.waitReason() : null)) ?? 'not-measured-yet' }, { level: 'warn' });
|
|
1553
|
+
reason: (stage === 'pool-slot' ? stageReason['pool-slot'] : (typeof pool?.waitReason === 'function' ? pool.waitReason(memoryLimit) : null)) ?? 'not-measured-yet' }, { level: 'warn' });
|
|
1348
1554
|
}, stallMs);
|
|
1349
1555
|
(timer as { unref?: () => void }).unref?.();
|
|
1350
1556
|
return () => clearTimeout(timer);
|
|
@@ -1387,7 +1593,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1387
1593
|
const acquireAdmission = async (): Promise<boolean> => {
|
|
1388
1594
|
if (!options.pool || typeof options.pool.acquireAdmission !== 'function') return true;
|
|
1389
1595
|
const stop = watchStall('admission');
|
|
1390
|
-
try { releaseAdmission = await options.pool.acquireAdmission(input.signal, after); return true; }
|
|
1596
|
+
try { releaseAdmission = await options.pool.acquireAdmission(input.signal, after, { memoryLimit }); return true; }
|
|
1391
1597
|
catch (error) {
|
|
1392
1598
|
// A predecessor closed without merging is not a cancellation: surface it so the queue owner is notified.
|
|
1393
1599
|
if (error instanceof Error && error.message.startsWith('pod lease predecessor blocked')) admissionBlocked = error.message;
|
|
@@ -1432,7 +1638,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1432
1638
|
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
|
|
1433
1639
|
let repairResultAnnounced = false;
|
|
1434
1640
|
let repairArtifactReady = false;
|
|
1435
|
-
const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples
|
|
1641
|
+
const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples> } => {
|
|
1436
1642
|
let full: ReturnType<Kubectl>;
|
|
1437
1643
|
try {
|
|
1438
1644
|
full = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
|
|
@@ -1442,10 +1648,12 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1442
1648
|
debug.log('self-implement.pod', unavailableEvent, { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
1443
1649
|
return { status: 'unavailable', childLedgerIncomplete: false, samples: [] };
|
|
1444
1650
|
}
|
|
1445
|
-
const via = /^ELANOUS_POD_SOURCE_VIA (mirror|github)\r?$/m.exec(full.stdout)?.[1];
|
|
1651
|
+
const via = /^ELANOUS_POD_SOURCE_VIA (image|mirror|github)\r?$/m.exec(full.stdout)?.[1];
|
|
1446
1652
|
// #28246 names why the mirror was skipped on the line before `VIA github`; carry it on the same event.
|
|
1447
1653
|
const mirrorSkip = /^ELANOUS_POD_SOURCE_MIRROR_SKIP (no-mount|rev-parse-failed|clone-failed)\r?$/m.exec(full.stdout)?.[1];
|
|
1448
|
-
|
|
1654
|
+
const imageSkip = /^ELANOUS_POD_SOURCE_IMAGE_SKIP ([^\r\n]+)\r?$/m.exec(full.stdout)?.[1];
|
|
1655
|
+
const delta = /^ELANOUS_POD_SOURCE_DELTA (mirror|origin)\r?$/m.exec(full.stdout)?.[1];
|
|
1656
|
+
if (via) debug.log('self-implement.pod', 'source-via', { job: name, via, ...(mirrorSkip ? { mirrorSkip } : {}), ...(imageSkip ? { imageSkip } : {}), ...(delta ? { delta } : {}) });
|
|
1449
1657
|
let ledgerIncomplete = false;
|
|
1450
1658
|
let childLedgerIncomplete = false;
|
|
1451
1659
|
let artifactIncomplete = false;
|
|
@@ -1478,7 +1686,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1478
1686
|
record('self-implement.pod', 'repair-artifact-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
1479
1687
|
}
|
|
1480
1688
|
}
|
|
1481
|
-
return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout)
|
|
1689
|
+
return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout) };
|
|
1482
1690
|
};
|
|
1483
1691
|
let retryAccount: string | undefined;
|
|
1484
1692
|
let retrySamples: ReturnType<typeof parseMemSamples> = [];
|
|
@@ -1521,12 +1729,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1521
1729
|
const jobArgs = zaiRoute === 'openrouter' ? zaiOverflowChildArgs(args, ZAI_OPENROUTER_FALLBACK_MODEL) : args;
|
|
1522
1730
|
const grok = options.provider === 'grok';
|
|
1523
1731
|
const openrouter = options.provider === 'openrouter' || zaiRoute === 'openrouter';
|
|
1732
|
+
const openGateway = options.provider === 'opengateway';
|
|
1524
1733
|
const anthropic = options.provider === 'anthropic';
|
|
1525
1734
|
const bedrockChild = options.provider === 'bedrock';
|
|
1526
1735
|
const zaiChild = zaiRoute === 'zai';
|
|
1527
1736
|
/** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` · bedrock·glm 은 이름 있는 Secret 으로만). */
|
|
1528
|
-
const apiKeyChild = openrouter || anthropic || bedrockChild || zaiChild;
|
|
1529
|
-
const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
|
|
1737
|
+
const apiKeyChild = openrouter || openGateway || anthropic || bedrockChild || zaiChild;
|
|
1738
|
+
const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : openGateway ? 'opengateway' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
|
|
1530
1739
|
debug.log('self-implement.pod', 'account', { spaceId: input.spaceId, account: plannedAccount, brokered: Boolean(options.accountBroker) });
|
|
1531
1740
|
const plannedCodexAccounts = grok || apiKeyChild || !options.accountBroker || !options.rotationAccounts ? undefined : [plannedAccount, ...options.rotationAccounts.filter((candidate) => candidate !== plannedAccount && candidate !== failedAccount)];
|
|
1532
1741
|
if (plannedCodexAccounts && !options.rotationAccounts!.includes(plannedAccount)) throw new Error(`pod: 배분 계정 ${plannedAccount} 이 회전 계획에 없다`);
|
|
@@ -1569,6 +1778,17 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1569
1778
|
}) : undefined;
|
|
1570
1779
|
const openrouterKey = openrouter ? env.OPENROUTER_API_KEY?.trim() || (options.readKeyCache ?? defaultReadKeyCache)('OPENROUTER_API_KEY')?.trim() : undefined;
|
|
1571
1780
|
if (openrouter && !openrouterKey) throw new Error('pod: Pod 에 openrouter 키 없음 — 호스트 키 캐시 또는 OPENROUTER_API_KEY 필요');
|
|
1781
|
+
const openGatewayEnvKey = openGateway ? env.OPENGATEWAY_API_KEY?.trim() : undefined;
|
|
1782
|
+
const openGatewayCachedKey = openGateway ? (options.readKeyCache ?? defaultReadKeyCache)('OPENGATEWAY_API_KEY')?.trim() : undefined;
|
|
1783
|
+
if (openGatewayEnvKey && openGatewayCachedKey && openGatewayEnvKey !== openGatewayCachedKey)
|
|
1784
|
+
throw new Error('pod: opengateway env와 키 캐시가 충돌 — Pod 발사 거부');
|
|
1785
|
+
const openGatewayHostConfig = openGateway ? (options.openGatewayConfig ?? getUserConfig)() : undefined;
|
|
1786
|
+
const openGatewayConfiguredKeys = openGatewayHostConfig ? configuredOpenGatewayKeys(openGatewayHostConfig.llm) : [];
|
|
1787
|
+
if (openGatewayConfiguredKeys.length > 1 || openGatewayConfiguredKeys.some((key) =>
|
|
1788
|
+
(openGatewayEnvKey && openGatewayEnvKey !== key) || (openGatewayCachedKey && openGatewayCachedKey !== key)))
|
|
1789
|
+
throw new Error('pod: opengateway 호스트 설정과 env/키 캐시가 충돌 — Pod 발사 거부');
|
|
1790
|
+
const openGatewayKey = openGatewayEnvKey || openGatewayCachedKey || openGatewayConfiguredKeys[0];
|
|
1791
|
+
if (openGateway && !openGatewayKey) throw new Error('pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요');
|
|
1572
1792
|
// POD-ANTHROPIC-PROVIDER — 제품 자격 해석(`getAnthropicApiKey` · 키 캐시 → env)으로 읽는다. 못 읽으면 Pod 를 안 띄운다(fail-closed).
|
|
1573
1793
|
// ⛔ 값은 로그·오류 문면에 안 싣는다 — «있었다» 만.
|
|
1574
1794
|
const anthropicKey = anthropic
|
|
@@ -1590,6 +1810,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1590
1810
|
}
|
|
1591
1811
|
bedrockSpec = checked.spec;
|
|
1592
1812
|
}
|
|
1813
|
+
let jobDeadlineSeconds = options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS;
|
|
1593
1814
|
const creds = grok
|
|
1594
1815
|
? (options.grokCredentials ?? (() => hostGrokCredentials({ env, apiKeyOptIn: options.grokApiKeyOptIn })))()
|
|
1595
1816
|
: apiKeyChild ? { ghToken: (options.openrouterGhToken ?? defaultGhToken)() }
|
|
@@ -1638,7 +1859,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1638
1859
|
}
|
|
1639
1860
|
}
|
|
1640
1861
|
// Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
|
|
1641
|
-
const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
|
|
1862
|
+
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'
|
|
1642
1863
|
&& key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
|
|
1643
1864
|
&& (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
|
|
1644
1865
|
const skillEnvs: Record<string, string> = options.skillEnv
|
|
@@ -1784,10 +2005,41 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1784
2005
|
authorChoice = { ...authorChoice, model: childModel, modelSource: 'fallback-child' };
|
|
1785
2006
|
} else debug.log('self-implement.pod', 'role-llm-armed', { job: name, role: 'planning', model: authorChoice.model, source: authorChoice.modelSource, account: roleCodexAccount });
|
|
1786
2007
|
}
|
|
1787
|
-
const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
|
|
2008
|
+
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);
|
|
1788
2009
|
const hostFallbackNames = (options.fallbackConfig ?? getUserConfig)().llm.fallbackChain;
|
|
1789
2010
|
const normalizedFallback = normalizeFallbackChain(hostFallbackNames);
|
|
1790
|
-
|
|
2011
|
+
let fallbackSteps = normalizedFallback.chain;
|
|
2012
|
+
let zaiFallbackSpec: ZaiPodSpec | undefined;
|
|
2013
|
+
const codexChild = options.provider === undefined || options.provider === 'openai-codex';
|
|
2014
|
+
if (!codexChild && fallbackSteps.includes('zai')) {
|
|
2015
|
+
fallbackSteps = fallbackSteps.filter(step => step !== 'zai');
|
|
2016
|
+
debug.log('self-implement.pod', 'zai-fallback-unavailable', {
|
|
2017
|
+
job: name, reason: 'non-codex-child',
|
|
2018
|
+
}, { level: 'warn' });
|
|
2019
|
+
}
|
|
2020
|
+
if (codexChild && fallbackSteps.includes('zai')) {
|
|
2021
|
+
const settings = (options.zaiSettings ?? (() => zaiSettingsFromConfig(getUserConfig().llm)))();
|
|
2022
|
+
const poolMembers = (options.pool as { members?: readonly PodPoolMember[] } | undefined)?.members;
|
|
2023
|
+
const contexts = Array.isArray(poolMembers) && poolMembers.length > 0 ? poolMembers.map((m) => m.context) : [context];
|
|
2024
|
+
let active: number | null = 0;
|
|
2025
|
+
for (const c of contexts) {
|
|
2026
|
+
const n = c === context ? countActiveZaiJobs(kubectl, namespace)
|
|
2027
|
+
: countActiveZaiJobs((a, i) => baseKubectl(['--context', c, ...a], i), namespace);
|
|
2028
|
+
if (n === null) { active = null; break; }
|
|
2029
|
+
active += n;
|
|
2030
|
+
}
|
|
2031
|
+
const slot = decideZaiPodSlot({ active, pending: zaiPendingApply.size,
|
|
2032
|
+
maxConcurrent: settings.maxConcurrent, overage: 'stop' });
|
|
2033
|
+
if (slot.route === 'zai') {
|
|
2034
|
+
const checked = checkZaiPodSecret(kubectl, namespace);
|
|
2035
|
+
if (checked.ok) { zaiFallbackSpec = checked.spec; zaiPendingApply.add(name); }
|
|
2036
|
+
else debug.log('self-implement.pod', 'zai-fallback-unavailable', { job: name, reason: 'secret-unavailable' }, { level: 'warn' });
|
|
2037
|
+
} else debug.log('self-implement.pod', 'zai-fallback-unavailable', {
|
|
2038
|
+
job: name, reason: slot.route === 'refuse' ? 'slot-refused' : 'slot-unavailable', active,
|
|
2039
|
+
}, { level: 'warn' });
|
|
2040
|
+
if (!zaiFallbackSpec) fallbackSteps = fallbackSteps.filter(step => step !== 'zai');
|
|
2041
|
+
}
|
|
2042
|
+
const fallbackChain = { steps: fallbackSteps, source: normalizedFallback.usedDefault ? 'default' as const : 'host' as const };
|
|
1791
2043
|
debug.log('self-implement.pod', 'fallback-chain-carried', {
|
|
1792
2044
|
job: name, source: fallbackChain.source, steps: fallbackChain.steps,
|
|
1793
2045
|
...(normalizedFallback.dropped.length ? { droppedCount: normalizedFallback.dropped.length } : {}),
|
|
@@ -1822,6 +2074,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1822
2074
|
// 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
|
|
1823
2075
|
// ☸️ 노드 레지스트리에 이 판이 있으면 그것을 pull 한다(델타 · 대표 2026-09-26) — 라벨 판정은 로컬 이미지 이름으로 한다.
|
|
1824
2076
|
const jobImage = member?.imageRef ?? image;
|
|
2077
|
+
// A pool member may pull a different image than the host's local one.
|
|
2078
|
+
// The local Docker label cannot prove that member image's commit.
|
|
2079
|
+
const localImageCommit = options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit;
|
|
2080
|
+
const jobImageCommit = jobImage === image ? localImageCommit : null;
|
|
1825
2081
|
const launchRunId = retryAccount || oomRetried || ghLoginRetried ? mintRunId() : childRunId;
|
|
1826
2082
|
let liveChildRunId = launchRunId;
|
|
1827
2083
|
// TA-POD-WARMBOOT-PARITY — the id the followed Job prints in its result marker (null = wait on the condition only).
|
|
@@ -1830,9 +2086,12 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1830
2086
|
let existingExecutionKey: string | undefined;
|
|
1831
2087
|
let existingAttempt = NaN;
|
|
1832
2088
|
let existingChildScript = '';
|
|
2089
|
+
let jobApplyStartedAt: number | undefined;
|
|
2090
|
+
let lifecycleJobUid: string | undefined;
|
|
1833
2091
|
const existing = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.metadata.uid} {.status.conditions[*].type}']);
|
|
1834
2092
|
const [existingUid = '', ...existingConditions] = existing.status === 0 ? existing.stdout.trim().split(/\s+/u) : [];
|
|
1835
2093
|
const hasExistingJob = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(existingUid);
|
|
2094
|
+
if (hasExistingJob) lifecycleJobUid = existingUid;
|
|
1836
2095
|
let reattach = hasExistingJob && !/Failed|FailureTarget/.test(existingConditions.join(' '));
|
|
1837
2096
|
const failedExisting = hasExistingJob && /Failed|FailureTarget/.test(existingConditions.join(' '));
|
|
1838
2097
|
if (hasExistingJob) {
|
|
@@ -1994,6 +2253,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1994
2253
|
slotId: env.ELANOUS_POD_SLOT_ID,
|
|
1995
2254
|
work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
|
|
1996
2255
|
};
|
|
2256
|
+
// TA-POD-WARMBOOT-PARITY shadow only: record the decision on the existing fresh-Job path.
|
|
2257
|
+
// Until a shared warm-candidate contract exists, evidence stays unmeasured and this never changes execution.
|
|
2258
|
+
if (workIdentity.taskCard) {
|
|
2259
|
+
const warmReuseConfig = options.warmReuseConfig?.() ?? (options.kubectl ? undefined : getUserConfig().pod?.warmReuse);
|
|
2260
|
+
const decision = decideWarmReuse('ta', warmReuseConfig, options.warmReuseEvidence?.());
|
|
2261
|
+
debug.log('pod.warm', 'ta-shadow-observation', { ...decision, execution: 'fresh-job' });
|
|
2262
|
+
}
|
|
1997
2263
|
// TA-POD-WARMBOOT-PARITY (GATE-WARM-BOOT parity · #27805): a commit-pinned source on a pool host — put that commit into
|
|
1998
2264
|
// the host mirror first (once per host·commit in this process) so podSourceScript finds it instead of fetching
|
|
1999
2265
|
// from GitHub. Best effort: a failed seed only logs; the Job is applied either way.
|
|
@@ -2034,13 +2300,51 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2034
2300
|
// TA-POD-WARMBOOT-PARITY (GATE-POD-RESULT-MARKER parity · #27815): this launch's marker is the child's last line.
|
|
2035
2301
|
const resultMarkerLine = resultMarkerOn ? (await import('./pod-result-marker-wait.js')).podResultMarkerShellLine(launchRunId) ?? undefined : undefined;
|
|
2036
2302
|
if (resultMarkerLine) markerToken = launchRunId;
|
|
2037
|
-
|
|
2303
|
+
// POD-GROK-VALIDITY-FIT — without a relay the Pod cannot renew the copy, so the Job ends before the token does.
|
|
2304
|
+
// Measured right before the Job is applied (review r2): setup between the credential check and here takes time.
|
|
2305
|
+
if (grokSubscription) {
|
|
2306
|
+
const shippedExpiresAtMs = shippedGrokExpiresAtMs((creds as { grokAuth: string }).grokAuth);
|
|
2307
|
+
const fit = fitPodDeadlineToGrok({ requestedSeconds: jobDeadlineSeconds, expiresAtMs: shippedExpiresAtMs, nowMs: (options.now ?? Date.now)(), relay: Boolean(credentialRelay) });
|
|
2308
|
+
if (fit.refused) {
|
|
2309
|
+
debug.log('self-implement.pod', 'grok-deadline-refused', { job: name, requestedSeconds: jobDeadlineSeconds, availableSeconds: fit.availableSeconds, minJobSeconds: POD_GROK_MIN_JOB_SECONDS, marginMs: POD_GROK_EXPIRY_MARGIN_MS });
|
|
2310
|
+
cleanupSecret();
|
|
2311
|
+
return { exitCode: 1, output: '', error: { code: 'pod-grok-expiring', message: `grok: 구독 access 토큰 남은 시간 ${formatGrokRemaining(fit.availableSeconds * 1000 + POD_GROK_EXPIRY_MARGIN_MS)} < Pod 발사 기준 ${formatGrokRemaining(POD_GROK_MIN_VALIDITY_MS)} — 발사 직전 재검증 · 다음: 호스트에서 grok login --device-auth 후 다시` } };
|
|
2312
|
+
}
|
|
2313
|
+
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 });
|
|
2314
|
+
jobDeadlineSeconds = fit.deadlineSeconds;
|
|
2315
|
+
}
|
|
2316
|
+
const job = podJobManifest({ 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 를 부르지 않는다
|
|
2038
2317
|
const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
|
|
2039
2318
|
const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
|
|
2319
|
+
jobApplyStartedAt = Date.now();
|
|
2040
2320
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
2041
2321
|
zaiPendingApply.delete(name); // apply 뒤엔 Job 라벨이 센다(성공) · 실패면 셀 것이 없다
|
|
2042
2322
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
2043
2323
|
releaseAdmission?.applied?.(name, context, namespace);
|
|
2324
|
+
const imageFacts = { job: name, childRunId: liveChildRunId, image: jobImage, imageCommit: jobImageCommit, context };
|
|
2325
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-job-image', data: imageFacts }, ledgerDir); }
|
|
2326
|
+
catch (error) { debug.log('self-implement.pod', 'job-image-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
2327
|
+
debug.log('self-implement.pod', 'job-image', imageFacts);
|
|
2328
|
+
// The injected observer does not acquire a production lease. The gate consumer
|
|
2329
|
+
// must compare the Job result and explicitly finalize before enabling replay.
|
|
2330
|
+
if (!oomRetried && input.warmWorkerCommand && options.warmPodRequest) {
|
|
2331
|
+
setImmediate(() => {
|
|
2332
|
+
try {
|
|
2333
|
+
const request = options.warmPodRequest!({ command: input.warmWorkerCommand! });
|
|
2334
|
+
void Promise.resolve(request)
|
|
2335
|
+
.then((result) => debug.log('pod.warm', 'request-observation', {
|
|
2336
|
+
spaceId: input.spaceId, status: result.status, reason: result.reason, execution: 'fresh-job',
|
|
2337
|
+
}))
|
|
2338
|
+
.catch((error) => debug.log('pod.warm', 'request-unavailable', {
|
|
2339
|
+
spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error), execution: 'fresh-job',
|
|
2340
|
+
}, { level: 'warn' }));
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
debug.log('pod.warm', 'request-unavailable', {
|
|
2343
|
+
spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error), execution: 'fresh-job',
|
|
2344
|
+
}, { level: 'warn' });
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2044
2348
|
// Logged only once the Job is applied — a failed apply carried nothing.
|
|
2045
2349
|
if (carriedBranch && carriedChildLlm) debug.log('self-implement.pod', 'child-llm-carried', { spaceId: input.spaceId, job: name, branch: carriedBranch, provider: carriedChildLlm.provider, model: carriedChildLlm.model ?? null });
|
|
2046
2350
|
if (options.source?.kind === 'bundle') {
|
|
@@ -2068,6 +2372,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2068
2372
|
}
|
|
2069
2373
|
// ☸️ P2: 비밀의 소유자 = Job — 호스트가 죽어도 Job 의 TTL 삭제와 함께 k8s 가 비밀을 거둔다(자격 사본이 클러스터에 남지 않는다).
|
|
2070
2374
|
const uid = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.metadata.uid}']).stdout.trim();
|
|
2375
|
+
lifecycleJobUid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(uid) ? uid : undefined;
|
|
2071
2376
|
if (uid) {
|
|
2072
2377
|
const owned = kubectl(['-n', namespace, 'patch', 'secret', `${name}-creds`, '--type=merge', '-p', JSON.stringify({ metadata: { ownerReferences: [{ apiVersion: 'batch/v1', kind: 'Job', name, uid }] } })]);
|
|
2073
2378
|
if (owned.status !== 0) debug.log('self-implement.pod', 'secret-owner-failed', { job: name, stderr: owned.stderr.trim() }, { level: 'warn' });
|
|
@@ -2086,7 +2391,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2086
2391
|
}
|
|
2087
2392
|
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
|
|
2088
2393
|
recorded = true;
|
|
2089
|
-
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 } : {}) });
|
|
2394
|
+
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 } : {}) });
|
|
2090
2395
|
let state: 'complete' | 'failed' | 'aborted' = 'failed';
|
|
2091
2396
|
let failedReason = '';
|
|
2092
2397
|
let containerReason: string | null = null;
|
|
@@ -2098,11 +2403,34 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2098
2403
|
// ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
|
|
2099
2404
|
let livePartialLine: string | null = null;
|
|
2100
2405
|
let pollsSinceCapture = 0;
|
|
2406
|
+
let imageSnapshotRecorded = false;
|
|
2407
|
+
let terminalImageRecorded = false;
|
|
2408
|
+
const recordPodImage = (final: boolean): void => {
|
|
2409
|
+
if (final ? terminalImageRecorded : imageSnapshotRecorded) return;
|
|
2410
|
+
const observed = readJobPodImage(kubectl, namespace, name, lifecycleJobUid);
|
|
2411
|
+
if (!final && !observed.podName) return;
|
|
2412
|
+
const append = (isFinal: boolean): void => {
|
|
2413
|
+
const data = { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1,
|
|
2414
|
+
image: jobImage, imageCommit: jobImageCommit, context, ...observed, final: isFinal };
|
|
2415
|
+
try {
|
|
2416
|
+
appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-image-digest', data }, ledgerDir);
|
|
2417
|
+
if (isFinal) terminalImageRecorded = true;
|
|
2418
|
+
else imageSnapshotRecorded = true;
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
debug.log('self-implement.pod', 'pod-image-digest-ledger-unavailable', {
|
|
2421
|
+
job: name, final: isFinal, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
2422
|
+
}
|
|
2423
|
+
debug.log('self-implement.pod', 'pod-image-digest', data);
|
|
2424
|
+
};
|
|
2425
|
+
if (final && !imageSnapshotRecorded && observed.podName) append(false);
|
|
2426
|
+
append(final);
|
|
2427
|
+
};
|
|
2101
2428
|
const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
|
|
2102
2429
|
const markerWait = markerToken ? await import('./pod-result-marker-wait.js') : null;
|
|
2103
2430
|
const waitStartedAt = Date.now();
|
|
2104
2431
|
for (;;) {
|
|
2105
2432
|
if (input.signal?.aborted) {
|
|
2433
|
+
recordPodImage(true);
|
|
2106
2434
|
// METER-POD-ROLLUP-GAP — Job 삭제 «전»에 로그를 한 번 거둔다: PARTIAL 줄이 삭제와 함께 사라지면 사용량이 0 처럼 보인다.
|
|
2107
2435
|
try {
|
|
2108
2436
|
const captured = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']);
|
|
@@ -2112,6 +2440,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2112
2440
|
state = 'aborted';
|
|
2113
2441
|
break;
|
|
2114
2442
|
}
|
|
2443
|
+
if (!imageSnapshotRecorded) recordPodImage(false);
|
|
2115
2444
|
const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
|
|
2116
2445
|
const types = g.stdout;
|
|
2117
2446
|
if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
|
|
@@ -2187,6 +2516,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2187
2516
|
}
|
|
2188
2517
|
await sleep(options.pollMs ?? 15_000);
|
|
2189
2518
|
}
|
|
2519
|
+
if (!terminalImageRecorded) recordPodImage(true);
|
|
2190
2520
|
let logs = '';
|
|
2191
2521
|
let logTailReason: string | undefined;
|
|
2192
2522
|
try {
|
|
@@ -2366,14 +2696,52 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2366
2696
|
}
|
|
2367
2697
|
const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
|
|
2368
2698
|
? lastPodChildFailure(logs) : null;
|
|
2699
|
+
let childPrFailure: ReturnType<typeof podPrOpenFailure> = null;
|
|
2700
|
+
if (childFailure) {
|
|
2701
|
+
try {
|
|
2702
|
+
const entries = loadRunLedger(liveChildRunId, ledgerDir);
|
|
2703
|
+
const entry = entries?.slice().reverse().find((row) => row.event === 'pr-open-failed');
|
|
2704
|
+
childPrFailure = podPrOpenFailure(entry?.data, childFailure.rawError);
|
|
2705
|
+
} catch { /* child ledger is optional; terminal result remains available */ }
|
|
2706
|
+
childPrFailure ??= podPrOpenFailure(null, childFailure.rawError);
|
|
2707
|
+
}
|
|
2708
|
+
if (childPrFailure && disposition) {
|
|
2709
|
+
disposition.childEvidence = {
|
|
2710
|
+
...(disposition.childEvidence ?? {}),
|
|
2711
|
+
prFailure: {
|
|
2712
|
+
stage: childPrFailure.prFailureStage,
|
|
2713
|
+
source: childPrFailure.prFailureSource,
|
|
2714
|
+
errorTail: childPrFailure.childErrorTail,
|
|
2715
|
+
...(childPrFailure.blockingPrNumber ? { blockingPrNumber: childPrFailure.blockingPrNumber } : {}),
|
|
2716
|
+
...(childPrFailure.candidateCardId ? { candidateCardId: childPrFailure.candidateCardId } : {}),
|
|
2717
|
+
...(childPrFailure.blockingCardId ? { blockingCardId: childPrFailure.blockingCardId } : {}),
|
|
2718
|
+
},
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2369
2721
|
let noResultDiagnostic: string | null = null;
|
|
2370
|
-
// POD-NORESULT:
|
|
2371
|
-
|
|
2722
|
+
// POD-NORESULT: a terminal Job without a parsed disposition is incomplete even when the Job condition says Complete.
|
|
2723
|
+
const noResultCandidate = !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' &&
|
|
2724
|
+
(state === 'complete' || (state === 'failed' && containerExitCode !== 0));
|
|
2725
|
+
if (noResultCandidate) {
|
|
2372
2726
|
try { noResultDiagnostic = podNoResultDiagnostic(readFileSync(runLedgerPath(liveChildRunId, ledgerDir), 'utf8')); }
|
|
2373
2727
|
catch { /* ledger not returned: do not invent a stage */ }
|
|
2374
2728
|
noResultDiagnostic ??= 'child terminal result missing; last ledger stage=unknown; round=unknown; mustFix=unknown';
|
|
2375
2729
|
}
|
|
2376
|
-
|
|
2730
|
+
// POD-NORESULT-LOG-TAIL: the Job runs with backoffLimit 0, so the failed Pod — and its container log —
|
|
2731
|
+
// is collected right after this. When the child ledger cannot even name the stage, read the tail here,
|
|
2732
|
+
// before deletion, so the parent keeps a maskable reason instead of `childEvidence=null`.
|
|
2733
|
+
let noResultLogEvidence: Record<string, unknown> | null = null;
|
|
2734
|
+
if (noResultDiagnostic?.includes('stage=unknown')) {
|
|
2735
|
+
const logRead = readPodNoResultLogTail({ run: kubectl, namespace, job: name, maxLines: POD_NO_RESULT_LOG_TAIL_LINES });
|
|
2736
|
+
if ('tail' in logRead) {
|
|
2737
|
+
noResultLogEvidence = { stderr: logRead.tail.join('\n'), logTail: logRead.tail, logTailLines: logRead.tail.length };
|
|
2738
|
+
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 } });
|
|
2739
|
+
} else {
|
|
2740
|
+
noResultLogEvidence = { stderr: `log-unavailable: ${logRead.unavailable}` };
|
|
2741
|
+
debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, logUnavailable: logRead.unavailable }, { level: 'warn' });
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error, ...(childPrFailure ?? {}) }, { compact: { stringMax: 500 } });
|
|
2377
2745
|
if (noResultDiagnostic && !noResultDiagnostic.includes('stage=unknown')) {
|
|
2378
2746
|
debug.log('self-implement.pod', 'no-result-ledger-diagnostic', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic }, { compact: { stringMax: 500 } });
|
|
2379
2747
|
}
|
|
@@ -2381,14 +2749,37 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2381
2749
|
...(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
|
|
2382
2750
|
? { ...(childFailure ? { childStage: childFailure.stage } : disposition?.stage ? { childStage: disposition.stage } : {}),
|
|
2383
2751
|
// 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).
|
|
2384
|
-
childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line')
|
|
2385
|
-
|
|
2752
|
+
childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line'),
|
|
2753
|
+
...(childPrFailure ?? {}) } : {}) },
|
|
2754
|
+
(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded') ? { compact: { stringMax: 500 } } : undefined);
|
|
2386
2755
|
// POD-LIFECYCLE-STAGES: one `pod.lifecycle stages` line — clone · mirror · run come from the Pod's own
|
|
2387
|
-
// `ELANOUS_POD_STAGE` markers (SI-POD-STAGE-MARKERS), read from
|
|
2756
|
+
// `ELANOUS_POD_STAGE` markers (SI-POD-STAGE-MARKERS), read from that Pod's full named log. ⛔ No `--tail=400` fallback:
|
|
2388
2757
|
// a tail can lose the real `run start` and pair a child-printed look-alike with the real `run end` (review mf) —
|
|
2389
2758
|
// when the full log cannot be read, every stage stays «missing».
|
|
2759
|
+
let k8sTiming: ReturnType<typeof siTaskPodK8sTiming> = {};
|
|
2760
|
+
try {
|
|
2761
|
+
const childRunLabel = k8sLabelValue(liveChildRunId);
|
|
2762
|
+
if (childRunLabel) {
|
|
2763
|
+
const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `elanous.child-run=${childRunLabel}`, '-o', 'json']);
|
|
2764
|
+
if (pods.status === 0) k8sTiming = siTaskPodK8sTiming(pods.stdout, childRunLabel, name, lifecycleJobUid);
|
|
2765
|
+
}
|
|
2766
|
+
} catch { /* observation only: keep each unavailable timestamp missing */ }
|
|
2767
|
+
let lifecyclePodStages: Record<string, number> = {};
|
|
2768
|
+
let lifecycleDepsSkipped: { at: number; reason: string } | undefined;
|
|
2769
|
+
if (k8sTiming.podName && k8sTiming.podUid) {
|
|
2770
|
+
try {
|
|
2771
|
+
// The stage markers and UID must come from exactly the same Pod. `logs job/<name>`
|
|
2772
|
+
// can choose another Pod when a Job has more than one attempt.
|
|
2773
|
+
const podLogs = kubectl(['-n', namespace, 'logs', k8sTiming.podName, '-c', 'child']);
|
|
2774
|
+
if (podLogs.status === 0) {
|
|
2775
|
+
lifecyclePodStages = parseSiTaskPodStages(podLogs.stdout);
|
|
2776
|
+
lifecycleDepsSkipped = parseSiTaskDepsSkipped(podLogs.stdout);
|
|
2777
|
+
}
|
|
2778
|
+
} catch { /* only this Pod's markers may fill lifecycle stages */ }
|
|
2779
|
+
}
|
|
2390
2780
|
emitPodLifecycle(siTaskPodLifecycleEvent({ runId: liveChildRunId, podJob: name, host: member?.context ?? null, watchStart: waitStartedAt, watchEnd: Date.now(),
|
|
2391
|
-
|
|
2781
|
+
createStart: jobApplyStartedAt,
|
|
2782
|
+
...k8sTiming, podStages: lifecyclePodStages, depsSkipped: lifecycleDepsSkipped, outcome: oomKilled ? 'oom' : state }));
|
|
2392
2783
|
if (oomKilled && reattach && !oomRetried && (!reattachedMemoryLimit || memoryGi(reattachedMemoryLimit) === null)) {
|
|
2393
2784
|
return { exitCode: 1, output: '', error: { code: 'pod-memory-unavailable', message: `Job ${name} 실제 메모리 한도를 확인할 수 없다` } };
|
|
2394
2785
|
}
|
|
@@ -2429,10 +2820,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2429
2820
|
appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-finished', data: { job: name, executionKey, fromChildRunId: retryFromChildRunId, childRunId: liveChildRunId, state } }, ledgerDir);
|
|
2430
2821
|
}
|
|
2431
2822
|
if (goalFile) {
|
|
2823
|
+
const noResultOutcome = state === 'failed' && containerExitCode !== null && noResultDiagnostic ? 'abandoned' : 'pod-no-result';
|
|
2432
2824
|
const record: GoalExecutionRecord = {
|
|
2433
2825
|
runId: liveChildRunId,
|
|
2434
2826
|
stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
|
|
2435
|
-
outcome: (!disposition ?
|
|
2827
|
+
outcome: (!disposition ? noResultOutcome : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
|
|
2436
2828
|
ok: state === 'complete' && (disposition?.ok ?? false),
|
|
2437
2829
|
...(disposition?.prNumber !== undefined ? { prNumber: disposition.prNumber } : {}),
|
|
2438
2830
|
completedAt: new Date().toISOString(),
|
|
@@ -2440,17 +2832,50 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2440
2832
|
try { const { appendGoalExecutionRecord } = await import('../../self-implement/orchestrator.js'); appendGoalExecutionRecord(goalFile, record); }
|
|
2441
2833
|
catch (error) { debug.log('self-implement.pod', 'goal-record-unavailable', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }); }
|
|
2442
2834
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2835
|
+
// The host still has the complete Pod log here. The CLI only retains a short stdout tail,
|
|
2836
|
+
// so preserve the parsed child facts in the parent ledger before that tail is returned.
|
|
2837
|
+
if (state === 'failed' || state === 'aborted' || noResultLogEvidence || (state === 'complete' && noResultDiagnostic !== null)) {
|
|
2838
|
+
// Every terminal attempt writes a marker, including one with no parseable child facts.
|
|
2839
|
+
// The next attempt must never inherit an earlier Pod's classification.
|
|
2840
|
+
const childEvidence = noResultLogEvidence ?? disposition?.childEvidence ?? null;
|
|
2841
|
+
try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence, childRunId: liveChildRunId, job: name } }, ledgerDir); }
|
|
2842
|
+
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' }); }
|
|
2843
|
+
}
|
|
2844
|
+
let prLookup: OpenPrByBranch | undefined;
|
|
2845
|
+
const terminalBranch = typeof disposition?.branch === 'string' ? disposition.branch.trim() : '';
|
|
2846
|
+
if (disposition && (state === 'failed' || state === 'aborted' || disposition.ok === false)
|
|
2847
|
+
&& (!Number.isSafeInteger(disposition.prNumber) || (disposition.prNumber ?? 0) <= 0) && terminalBranch) {
|
|
2848
|
+
try { prLookup = await (options.lookupOpenPrByBranch ?? defaultLookupOpenPrByBranch)(terminalBranch, repoUrl); }
|
|
2849
|
+
catch { prLookup = { status: 'failed', reason: 'pr-lookup-threw' }; }
|
|
2850
|
+
}
|
|
2851
|
+
const terminalDisposition = prLookup?.status === 'found' && disposition
|
|
2852
|
+
? { ...disposition, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl } : disposition;
|
|
2853
|
+
if (prLookup?.status === 'found') debug.log('self-implement.pod', 'pr-terminal-bound-by-branch', {
|
|
2854
|
+
branch: terminalBranch, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl, status: 'found', job: name });
|
|
2855
|
+
else if (prLookup) debug.log('self-implement.pod', 'pr-terminal-branch-lookup', {
|
|
2856
|
+
branch: terminalBranch, status: prLookup.status, reason: prLookup.reason, job: name });
|
|
2857
|
+
const terminalRecord = { ...classifyPodTerminal({ childRunId: liveChildRunId, state, disposition: terminalDisposition, env,
|
|
2858
|
+
prBody: options.draftResidue || prLookup?.status === 'found' ? undefined : podTerminalPrBody(terminalDisposition),
|
|
2859
|
+
goalText: parentGoal ?? input.feature, at: new Date().toISOString() }),
|
|
2860
|
+
...(prLookup ? { prLookup: prLookup.status === 'found' ? { status: 'found', source: 'branch-lookup' }
|
|
2861
|
+
: { status: prLookup.status, reason: prLookup.reason } } : {}),
|
|
2862
|
+
...(prLookup?.status === 'found' ? { prNumberSource: 'branch-lookup' } : {}) };
|
|
2863
|
+
try {
|
|
2864
|
+
if (options.draftResidue) await options.draftResidue(terminalRecord, { ledgerRunId, ledgerDir });
|
|
2865
|
+
else if (prLookup?.status === 'found') {
|
|
2866
|
+
// This lookup only repairs the host ledger's PR identity. The draft sweeper owns PR writes.
|
|
2867
|
+
const alreadyRecorded = loadRunLedger(ledgerRunId, ledgerDir)?.some((entry) =>
|
|
2868
|
+
entry.event === 'pr-terminal' && entry.data.childRunId === liveChildRunId);
|
|
2869
|
+
if (!alreadyRecorded) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pr-terminal', data: terminalRecord }, ledgerDir);
|
|
2870
|
+
} else await applyPodTerminal(terminalRecord, { ledgerRunId, ledgerDir });
|
|
2871
|
+
}
|
|
2447
2872
|
catch (error) { debug.log('draft.residue', 'failed', { childRunId: liveChildRunId, prNumber: terminalRecord.prNumber,
|
|
2448
2873
|
terminalClass: terminalRecord.terminalClass, owner: terminalRecord.owner, ownerSource: terminalRecord.ownerSource,
|
|
2449
2874
|
step: 'apply', reason: error instanceof Error ? error.message : String(error) }); }
|
|
2450
2875
|
const tail = podRunResultLine(logs, salvage);
|
|
2451
2876
|
if (state === 'aborted') return { exitCode: null, output: tail, error: { code: 'aborted', message: 'aborted — Job deleted' }, ...(disposition ? { disposition } : {}) };
|
|
2452
2877
|
const deadlineExceeded = state === 'failed' && failedReason === 'DeadlineExceeded';
|
|
2453
|
-
const deadlineSeconds =
|
|
2878
|
+
const deadlineSeconds = jobDeadlineSeconds;
|
|
2454
2879
|
if (deadlineExceeded) {
|
|
2455
2880
|
debug.log('self-implement.pod', 'deadline-exceeded', {
|
|
2456
2881
|
job: name,
|
|
@@ -2465,7 +2890,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2465
2890
|
: disposition;
|
|
2466
2891
|
const podExitCode = state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1;
|
|
2467
2892
|
// SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
|
|
2468
|
-
const
|
|
2893
|
+
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;
|
|
2894
|
+
const podReason = rawPodReason
|
|
2895
|
+
? noResultLogEvidence ? boundedPodLogText(rawPodReason, POD_NO_RESULT_LOG_REASON_MAX_CHARS) : rawPodReason
|
|
2896
|
+
: undefined;
|
|
2469
2897
|
if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
|
|
2470
2898
|
if (podExitCode === 1) {
|
|
2471
2899
|
// HARNESS-FULL-GRAPH — 실패 쪽 두 노드: 결과 해석(result-parse) → 수확(salvage, Pod Job 이 push 한 줄로 판정).
|
|
@@ -2483,7 +2911,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2483
2911
|
? { error: { code: 'pod-gh-login-failed', message: `Job ${name} GitHub 앱 로그인 실패 (container=${containerReason ?? 'unknown'}/7, attempt=${ghLoginRetried ? 2 : 1}) — 새 토큰으로 ${ghLoginRetried ? '1회 재시도했으나 다시 실패' : '재시도 불가'}` } }
|
|
2484
2912
|
: oomKilled
|
|
2485
2913
|
? { 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))}` } }
|
|
2486
|
-
|
|
2914
|
+
: { 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}` : ''}` } }
|
|
2487
2915
|
: podReason && !childFailure?.error ? { error: { code: 'pod-child-failed', message: podReason } } : {}),
|
|
2488
2916
|
...(finishedDisposition ? { disposition: finishedDisposition } : {}),
|
|
2489
2917
|
};
|
|
@@ -2551,7 +2979,43 @@ export function recordPodSalvage(logs: string, job: string, log: (category: stri
|
|
|
2551
2979
|
return rows.map((row) => row.branch);
|
|
2552
2980
|
}
|
|
2553
2981
|
|
|
2554
|
-
|
|
2982
|
+
type PodPrOpenFailure = {
|
|
2983
|
+
childErrorTail: string;
|
|
2984
|
+
prFailureStage: 'pr-open-failed';
|
|
2985
|
+
prFailureSource: 'child-ledger' | 'terminal-result';
|
|
2986
|
+
blockingPrNumber?: number;
|
|
2987
|
+
candidateCardId?: string;
|
|
2988
|
+
blockingCardId?: string;
|
|
2989
|
+
};
|
|
2990
|
+
|
|
2991
|
+
/** Preserve the actionable tail independently of the bounded human error. Never infer a card from a PR number. */
|
|
2992
|
+
function podPrOpenFailure(ledger: Record<string, unknown> | undefined | null, terminalError: string): PodPrOpenFailure | null {
|
|
2993
|
+
const ledgerError = typeof ledger?.error === 'string' ? ledger.error : '';
|
|
2994
|
+
// A previous rework round can leave a pr-open-failed row behind. Only a terminal PR failure may consume it.
|
|
2995
|
+
if (!/draft PR 생성 실패|PR (?:gh|guard) 실패|same-cell reuse:/u.test(terminalError)) return null;
|
|
2996
|
+
// The terminal must contain the complete ledger error. A shared prefix such as "same-cell"
|
|
2997
|
+
// cannot distinguish an earlier rework round from the current failed attempt.
|
|
2998
|
+
const matchingLedger = ledgerError.length >= 20 && terminalError.includes(ledgerError);
|
|
2999
|
+
const source = terminalError || ledgerError;
|
|
3000
|
+
const sanitized = redactSecretText(source).replace(/\/home\/[^/\s]+\//gu, '~/');
|
|
3001
|
+
const tail = sanitized.split(/\r\n|\n|\r/u).filter(Boolean).at(-1) ?? sanitized;
|
|
3002
|
+
const terminalHasIds = /(?:blockers=|blockingPr=|candidateCardId=)/u.test(terminalError);
|
|
3003
|
+
const detail = `${terminalError}\n${matchingLedger ? ledgerError : ''}`;
|
|
3004
|
+
const blocker = /(?:blockers=|blockingPr=)\s*#?(\d+)(?::([A-Za-z0-9_-]{1,128}))?/u.exec(detail);
|
|
3005
|
+
const prNumber = blocker ? Number(blocker[1]) : null;
|
|
3006
|
+
const candidate = /candidateCardId=([A-Za-z0-9_-]{1,128})/u.exec(detail)?.[1];
|
|
3007
|
+
const card = blocker?.[2];
|
|
3008
|
+
return {
|
|
3009
|
+
childErrorTail: tail.slice(-240),
|
|
3010
|
+
prFailureStage: 'pr-open-failed',
|
|
3011
|
+
prFailureSource: terminalHasIds || !matchingLedger ? 'terminal-result' : 'child-ledger',
|
|
3012
|
+
...(prNumber && Number.isSafeInteger(prNumber) ? { blockingPrNumber: prNumber } : {}),
|
|
3013
|
+
...(candidate && candidate !== 'unknown' ? { candidateCardId: candidate } : {}),
|
|
3014
|
+
...(card && card !== 'unknown' ? { blockingCardId: card } : {}),
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
function lastPodChildFailure(logs: string): { stage: string; error: string; rawError: string } | null {
|
|
2555
3019
|
for (const line of logs.split('\n').reverse()) {
|
|
2556
3020
|
if (!line.trimStart().startsWith('{')) continue;
|
|
2557
3021
|
let result: unknown;
|
|
@@ -2561,14 +3025,67 @@ function lastPodChildFailure(logs: string): { stage: string; error: string } | n
|
|
|
2561
3025
|
const terminal = podTerminalRow(row);
|
|
2562
3026
|
if (!terminal) continue;
|
|
2563
3027
|
if (terminal.ok !== false || typeof terminal.error !== 'string') return null;
|
|
2564
|
-
const lines = terminal.error.replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
|
|
3028
|
+
const lines = redactSecretText(terminal.error).replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
|
|
2565
3029
|
const first = lines[0]!.slice(0, 240);
|
|
2566
3030
|
const last = lines.at(-1)!.slice(0, 240);
|
|
2567
|
-
return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}
|
|
3031
|
+
return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}`, rawError: terminal.error };
|
|
2568
3032
|
}
|
|
2569
3033
|
return null;
|
|
2570
3034
|
}
|
|
2571
3035
|
|
|
3036
|
+
/** POD-NORESULT-LOG-TAIL: how many container-log lines a failed Pod leaves behind for the parent. */
|
|
3037
|
+
export const POD_NO_RESULT_LOG_TAIL_LINES = 80;
|
|
3038
|
+
const POD_NO_RESULT_LOG_TAIL_LINE_CHARS = 512;
|
|
3039
|
+
const POD_NO_RESULT_LOG_TAIL_MAX_CHARS = 8_192;
|
|
3040
|
+
const POD_NO_RESULT_LOG_REASON_MAX_CHARS = 512;
|
|
3041
|
+
|
|
3042
|
+
function boundedPodLogText(source: string, maxChars: number): string {
|
|
3043
|
+
const redacted = redactSecretText(source).replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
3044
|
+
return redacted.length > maxChars ? `${redacted.slice(0, maxChars)}…[truncated]` : redacted;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
/** 로그 꼬리 — 마지막 N줄만, 한 줄씩 비밀값 마스킹. */
|
|
3048
|
+
export function podNoResultLogTailLines(source: string, maxLines: number = POD_NO_RESULT_LOG_TAIL_LINES): string[] {
|
|
3049
|
+
const lines = source.replace(/\r\n/g, '\n').split('\n');
|
|
3050
|
+
while (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
3051
|
+
const result: string[] = [];
|
|
3052
|
+
let chars = 0;
|
|
3053
|
+
for (const line of lines.slice(-Math.max(0, maxLines)).reverse()) {
|
|
3054
|
+
if (chars >= POD_NO_RESULT_LOG_TAIL_MAX_CHARS) break;
|
|
3055
|
+
const bounded = boundedPodLogText(line, Math.min(POD_NO_RESULT_LOG_TAIL_LINE_CHARS, POD_NO_RESULT_LOG_TAIL_MAX_CHARS - chars));
|
|
3056
|
+
if (!bounded) continue;
|
|
3057
|
+
result.push(bounded);
|
|
3058
|
+
chars += bounded.length;
|
|
3059
|
+
}
|
|
3060
|
+
return result.reverse();
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
export type PodNoResultLogRead = { tail: string[] } | { unavailable: string };
|
|
3064
|
+
|
|
3065
|
+
/**
|
|
3066
|
+
* POD-NORESULT-LOG-TAIL: the terminal Job (`backoffLimit 0`) is collected right after it fails, so the
|
|
3067
|
+
* container log has to be read while the Pod still exists. Never throws — an unreadable log becomes a reason.
|
|
3068
|
+
*/
|
|
3069
|
+
export function readPodNoResultLogTail(args: {
|
|
3070
|
+
run: (args: readonly string[]) => { status: number | null; stdout: string; stderr: string };
|
|
3071
|
+
namespace: string;
|
|
3072
|
+
job: string;
|
|
3073
|
+
maxLines?: number;
|
|
3074
|
+
}): PodNoResultLogRead {
|
|
3075
|
+
const maxLines = args.maxLines ?? POD_NO_RESULT_LOG_TAIL_LINES;
|
|
3076
|
+
try {
|
|
3077
|
+
const result = args.run(['-n', args.namespace, 'logs', `job/${args.job}`, `--tail=${maxLines}`]);
|
|
3078
|
+
if (result.status !== 0) {
|
|
3079
|
+
const reason = boundedPodLogText(result.stderr || result.stdout || '', POD_NO_RESULT_LOG_REASON_MAX_CHARS);
|
|
3080
|
+
return { unavailable: reason || `kubectl logs exit ${result.status}` };
|
|
3081
|
+
}
|
|
3082
|
+
const tail = podNoResultLogTailLines(result.stdout, maxLines);
|
|
3083
|
+
return tail.length ? { tail } : { unavailable: 'empty log output' };
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
return { unavailable: boundedPodLogText(error instanceof Error ? error.message : String(error), POD_NO_RESULT_LOG_REASON_MAX_CHARS) || 'unknown error' };
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
|
|
2572
3089
|
/** 사람용 런 결과 한 줄 — 수확 브랜치가 있으면 «수확할 브랜치: …» 를 붙인다. */
|
|
2573
3090
|
export function podRunResultLine(logs: string, branches: readonly string[]): string {
|
|
2574
3091
|
const result = logs.split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')).join('\n').slice(-4000);
|