elanous 0.2.34 → 0.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → 053a429e.9a63436527cc751c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +287 -0
- package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +64 -0
- package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +36 -0
- package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +149 -0
- package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 2683.c9773a0c589b031b.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 275-02ea4c2a33980422.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{ffdc3a04.b9c23c1b28dd4a96.js → 2812ca6a.3b9f4a7a08885744.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 2986.45d7652e4f13557e.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3031-85328a92fcce4da6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8817.61bb7ffbba502ecb.js → 3136.30aaaa5fa03662a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3148-98efd4869d660a87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3498.cf2a15a8a9e5b926.js → 3155.2587a55196ecb394.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3103.84470c8445962f04.js → 3324.a09703033abcbd2f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +8 -0
- package/apps/pwa/out/_next/static/chunks/3410.7f9af5ab77cbe65f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/{3819.163d4777f0a597f6.js → 3538.a445e50bb359d068.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +286 -0
- package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 3727.7949ccaacaa71242.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → 3a3d9966.cc4d4585a2708e7c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4116.a84a92a2492c080e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +104 -0
- package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 4286.b70c4520306817e8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4304.e9b2512834a9e23e.js +23 -0
- package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 4615.8525eb0add35eba6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +148 -0
- package/apps/pwa/out/_next/static/chunks/{3411-f87314c6c66ebc73.js → 5085-bce4904931767ca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 528.38ce20ef8aa251ff.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +122 -0
- package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 552.b7d061ad91eeac51.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3837.722e053fb9f321ad.js → 5711.957b095705c12560.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 575.d929793a53f27c4b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7562.948255cbc3e79d33.js → 599.c39c571b6733f2a2.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +79 -0
- package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6566.926a1d1b7c911442.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +1 -0
- package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +27 -0
- package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5446.edf141cff9ef647d.js → 6925.562d532110879886.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 6943.c6b24124853a47b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 7481.39f7a4654907bf6e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 771.c3cdb6c1f7f47c02.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/7784.1f0ac4a16d190cf4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 7891.03f910124a737985.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8037.e254fd797aa224d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +1 -0
- package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → 873d6c12.09bd499f57171543.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 8904.7d51af2dc033965c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +90 -0
- package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 9250-072df22eaadaa6b5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +77 -0
- package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 9514.131c52cc75fe6439.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9f31e478.fefcbcd3c388ebc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/{page-4fb31eb476dcbad3.js → page-083747f1d4518f61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e7abc34a48a6f71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-c15958835534404b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → ceba0382.650712b56b85b582.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/da5ecc5c-b005ad8cac737d57.js +1 -0
- package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +1 -0
- package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.mjs +3 -2
- package/catalog/providers/opengateway.yaml +25 -0
- package/docs/goal-context/01-goal-authoring-checks.md +8 -0
- package/docs/goal-context/03-verification-menu.md +24 -0
- package/docs/goal-context/04-mock-scenarios.md +100 -0
- package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
- package/docs/goal-context/README.md +7 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/default-loop.yaml +63 -4
- package/graphs/document-loop.yaml +51 -3
- package/graphs/implement-loop.yaml +77 -4
- package/graphs/journey/ta-journey.yaml +11 -1
- package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
- package/graphs/journey/units/author-dialogue/observe.ts +96 -0
- package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
- package/graphs/operate-loop.yaml +51 -3
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +17 -4
- package/graphs/research-loop.yaml +137 -3
- package/graphs/test-diet/test-diet.yaml +3 -0
- package/graphs/wizard/recipes.yaml +44 -0
- package/graphs/wizard/wizard-loop.yaml +44 -0
- package/graphs/work/research-report.yaml +2 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +139 -5
- package/scripts/coord-role-pack.ts +19 -6
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/hq/standby-snapshot.ts +17 -12
- package/scripts/release-loop/docs-land-node.ts +21 -5
- package/scripts/release-loop/docs-node.ts +204 -8
- package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
- package/scripts/release-loop/gate-node.ts +427 -72
- package/scripts/release-loop/gate-partial.ts +65 -11
- package/scripts/release-loop/gate-radius-live.ts +177 -0
- package/scripts/release-loop/gate-radius-shadow.ts +1 -1
- package/scripts/release-loop/node-verdict.ts +14 -1
- package/scripts/release-loop/npm-publish-node.ts +66 -12
- package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
- package/scripts/release-loop/pwa-remote.ts +166 -0
- package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +78 -15
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/test-diet/lib.ts +6 -1
- package/scripts/test-diet/node.ts +194 -13
- package/scripts/ux-sim.ts +92 -0
- package/skills/README.md +1 -0
- package/src/acp/core-turn-bridge.ts +48 -6
- package/src/agent/delegate-hint.ts +16 -0
- package/src/agent/runner.ts +82 -2
- package/src/agent/self-ambient-cache.ts +8 -0
- package/src/agent/task-notification.ts +56 -10
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/acp-reviewer.ts +39 -4
- package/src/agent-substrate/pr-reviewer.ts +72 -3
- package/src/agent-substrate/review-intent.ts +29 -2
- package/src/agent-substrate/review-observation.ts +3 -0
- package/src/agent-substrate/self-review-cli.ts +229 -28
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +240 -41
- package/src/boot/daemon-tools/self-implement.ts +1 -0
- package/src/budget/run-usage-rollup.ts +1 -0
- package/src/budget/usage-outcomes.ts +8 -4
- package/src/capture/tui-self-report.ts +102 -0
- package/src/chat/goal-continue-verdict.ts +41 -0
- package/src/chat/index.ts +30 -6
- package/src/chat/internal-turn-origin.ts +1 -0
- package/src/chat/paste-fold.ts +86 -0
- package/src/chat/pickers/modal-runtime.ts +10 -27
- package/src/chat/tool-render/block.ts +32 -0
- package/src/chat/tool-render/dispatch.ts +2 -20
- package/src/chat/tool-render/result-text.ts +98 -0
- package/src/chat/turn-typeahead.ts +70 -7
- package/src/cli/agent-cli.ts +13 -2
- package/src/cli/config-audit.ts +119 -0
- package/src/cli/doctor-cli.ts +26 -1
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/herdr-cli.ts +34 -0
- package/src/cli/light-entry-route.ts +50 -7
- package/src/cli/log-event-names.ts +2 -0
- package/src/cli/make-cli.ts +247 -11
- package/src/cli/msg-cli.ts +117 -3
- package/src/cli/pod-cli.ts +77 -3
- package/src/cli/pr-cli.ts +34 -18
- package/src/cli/pr-land-base-read.ts +11 -2
- package/src/cli/pr-land-heavy-remote.ts +6 -3
- package/src/cli/pr-land-overlap-evidence.ts +23 -4
- package/src/cli/pr-land-pod-shadow-job.ts +3 -0
- package/src/cli/pr-land-recovery.ts +117 -1
- package/src/cli/pty-takeover-cli.ts +8 -7
- package/src/cli/release-checklist-read-cli.ts +79 -0
- package/src/cli/release-cli.ts +92 -106
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/remote-resolve.ts +1 -1
- package/src/cli/root-tui-entry.ts +48 -0
- package/src/cli/sim-cli.ts +22 -0
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +1065 -338
- package/src/cli/tasks-read-cli.ts +197 -0
- package/src/cli/usage-cli.ts +13 -11
- package/src/clipboard/history.ts +179 -13
- package/src/config.ts +8 -1
- package/src/core-turn/run-core-turn.ts +4 -2
- package/src/core-turn/run-goal-loop.ts +73 -16
- package/src/core-turn/types.ts +6 -1
- package/src/dashboard/chat/acp-chat.ts +8 -0
- package/src/dashboard/context/keys.ts +8 -31
- package/src/dashboard/index.ts +280 -59
- package/src/dashboard/input/agent-wake.ts +7 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
- package/src/dashboard/input/chat-main-slash-command.ts +4 -2
- package/src/dashboard/input/goal-continue.ts +210 -0
- package/src/dashboard/input/goal-interim.ts +2 -0
- package/src/dashboard/input/internal-turn.ts +2 -8
- package/src/dashboard/input/seat-autowake.ts +9 -7
- package/src/dashboard/input/turn-autoheal.ts +52 -0
- package/src/dashboard/render/theme-resolver.ts +33 -4
- package/src/dashboard/seat-boot-preamble.ts +132 -5
- package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
- package/src/dashboard/slash-runtime/model-choices.ts +15 -0
- package/src/dashboard/stale-install-check.ts +3 -99
- package/src/debug/batch-queue.ts +119 -0
- package/src/debug/tui-log-tag.ts +67 -0
- package/src/display/agent-surface.ts +7 -0
- package/src/domains/schedule-registry.ts +16 -2
- package/src/domains/standalone-log-sink.ts +15 -2
- package/src/esc-abort-gate.ts +69 -0
- package/src/git-fs/idle-refresh.ts +76 -0
- package/src/git-fs/index.ts +18 -7
- package/src/git-fs/worktree.ts +137 -5
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-cli.ts +18 -0
- package/src/graph-runner/runner.ts +173 -21
- package/src/graph-wizard/generate.ts +108 -4
- package/src/graph-wizard/steps.ts +15 -0
- package/src/grok/credential.ts +10 -3
- package/src/grounding/proactive-ground.ts +86 -0
- package/src/harness/control-inbox.ts +49 -1
- package/src/harness/harness-cli-command.ts +163 -35
- package/src/harness/harness-incidents.ts +85 -1
- package/src/harness/launch-utterance-ledger.ts +208 -0
- package/src/harness/pod-control-send.ts +33 -5
- package/src/harness/self-send-remote.ts +69 -0
- package/src/harness/self-send-routing-guard.ts +6 -0
- package/src/harness/self-send-target.ts +4 -3
- package/src/impl-discipline/detector.ts +16 -4
- package/src/impl-discipline/system-prompt.ts +11 -0
- package/src/impl-discipline/types.ts +2 -2
- package/src/index.ts +822 -154
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/intake-plane/already-built-hint.ts +4 -2
- package/src/intake-plane/requirement-funnel.ts +29 -1
- package/src/light-cli.ts +32 -11
- package/src/llm/opengateway-models.ts +11 -0
- package/src/llm/provider-credentials.ts +14 -0
- package/src/llm/provider-detect.ts +8 -1
- package/src/llm/runtime-model-family.ts +2 -1
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +247 -59
- package/src/loops/msg-wake.ts +35 -39
- package/src/loops/orchestrator/inbox.ts +59 -11
- package/src/loops/orchestrator/knowhow-pack.ts +364 -0
- package/src/loops/orchestrator/tick.ts +5 -1
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/model-tier/llm-tier-map.ts +15 -1
- package/src/models/prompts.ts +17 -2
- package/src/msg/msg-post-office.ts +116 -0
- package/src/mss/logging/log-store.ts +79 -22
- package/src/native-tool-catalog.ts +15 -1
- package/src/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +20 -2
- package/src/nexus/api/loop-resources.ts +13 -1
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/oauth/codex.ts +7 -0
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/overview-collect.ts +30 -9
- package/src/ops/overview.ts +22 -9
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- package/src/pr-tool/census.ts +261 -27
- package/src/prompt-library/session-guidance.ts +14 -0
- package/src/provider-summary.ts +8 -0
- package/src/pty-shell/herdr-agent-report.ts +118 -9
- package/src/pty-shell/herdr-prompt.ts +266 -0
- package/src/pty-shell/pty-control-ipc.ts +151 -8
- package/src/pty-shell/pty-manifest.ts +7 -0
- package/src/pty-shell/pty-special-keys.ts +42 -2
- package/src/pty-shell/stale-install-check.ts +99 -0
- package/src/pty-shell/tui-run-state.ts +14 -2
- package/src/release-loop/auto-start.ts +20 -2
- package/src/release-loop/checklist.ts +100 -4
- package/src/release-loop/landed-but-yellow.ts +129 -5
- package/src/release-loop/light-rc.ts +1 -0
- package/src/release-loop/rehearsal.ts +37 -21
- package/src/repl/index.ts +192 -17
- package/src/repl/ops-msg-bridge.ts +32 -0
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/self-dev/ask-launch-flow.ts +44 -5
- package/src/self-dev/cli-help-text.ts +17 -0
- package/src/self-dev/decompose-proposal.ts +172 -20
- package/src/self-dev/decompose.ts +61 -0
- package/src/self-dev/dev-cli.ts +48 -12
- package/src/self-dev/dev-pipeline.ts +8 -2
- package/src/self-dev/draft-residue.ts +37 -1
- package/src/self-dev/draft-sweep.ts +137 -11
- package/src/self-dev/draft-triage-rules.ts +138 -0
- package/src/self-dev/fleet-reduce.ts +3 -2
- package/src/self-dev/glm-credential-guard.ts +77 -0
- package/src/self-dev/graph-front-nodes.ts +112 -0
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/graph-journey-route.ts +12 -2
- package/src/self-dev/harness-run-cli.ts +3 -15
- package/src/self-dev/orchestrate-cli-text.ts +10 -0
- package/src/self-dev/orchestrate-cli.ts +68 -3
- package/src/self-dev/orchestrate.ts +116 -17
- package/src/self-dev/review-provider-fallback.ts +35 -1
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-dev/self-orchestrate-runtime.ts +1 -11
- package/src/self-dev/shard-aggregate.ts +42 -5
- package/src/self-dev/shard-run.ts +8 -1
- package/src/self-health/autoheal.ts +153 -0
- package/src/self-health/signatures.ts +6 -0
- package/src/self-implement/blocked-draft-policy.ts +20 -0
- package/src/self-implement/classify-shadow.ts +3 -7
- package/src/self-implement/device-first.ts +116 -0
- package/src/self-implement/doctor-visit.ts +257 -2
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/gate-baseline.ts +56 -17
- package/src/self-implement/gate-cli.ts +1 -0
- package/src/self-implement/gate-remote.ts +31 -20
- package/src/self-implement/goal-author-cli.ts +1 -0
- package/src/self-implement/goal-author-decompose.ts +534 -45
- package/src/self-implement/goal-author.ts +282 -7
- package/src/self-implement/goal-elaborate.ts +449 -0
- package/src/self-implement/goal-refine.ts +273 -0
- package/src/self-implement/graph-overlay-yaml.ts +14 -3
- package/src/self-implement/graph-variant.ts +40 -6
- package/src/self-implement/headless-elanous-driver.ts +19 -3
- package/src/self-implement/off-diff-evidence.ts +58 -6
- package/src/self-implement/orchestrator.ts +370 -62
- package/src/self-implement/pod-warm-pool-profile.ts +1 -0
- package/src/self-implement/pwa-import-graph.ts +1 -1
- package/src/self-implement/rework-budget-judgment.ts +5 -8
- package/src/self-implement/rework-policy.ts +30 -0
- package/src/self-implement/run-ledger.ts +108 -0
- package/src/self-implement/run-stop.ts +10 -1
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/running-runs.ts +29 -1
- package/src/self-implement/scope-adjacency.ts +115 -0
- package/src/self-implement/seams.ts +187 -28
- package/src/self-implement/stop-autoheal.ts +87 -1
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/session-runtime/index.ts +1 -1
- package/src/setup/llm-env-detect.ts +5 -0
- package/src/shell-env-bootstrap.ts +11 -4
- package/src/skills/runner.ts +4 -2
- package/src/skills/tools/agent.ts +408 -40
- package/src/skills/tools/msg-ask.ts +100 -0
- package/src/skills/tools/ops-ledger.ts +2 -1
- package/src/skills/tools/schedule-wakeup.ts +23 -4
- package/src/skills/tools/shell.ts +61 -6
- package/src/skills/tools/terminal-modal-inject.ts +1 -2
- package/src/skills/tools/terminal-modal.ts +10 -8
- package/src/skills/url-router.ts +41 -1
- package/src/smart-wizard/run.ts +66 -0
- package/src/status/bar.ts +34 -6
- package/src/status/status-provider-info.ts +37 -6
- package/src/steward/hold-hygiene.ts +24 -6
- package/src/steward/rejudge-tick.ts +10 -2
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/steward/triage-budget.ts +194 -0
- package/src/steward/triage.ts +20 -3
- package/src/task-agent/actions.ts +72 -2
- package/src/task-agent/green-sweep.ts +4 -0
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +182 -10
- package/src/task-agent/mission.ts +30 -0
- package/src/task-agent/parent-host.ts +123 -3
- package/src/task-agent/rejudge-on-head.ts +15 -0
- package/src/task-agent/repair-cycle.ts +138 -32
- package/src/task-agent/repair-next-move.ts +31 -3
- package/src/task-agent/repair-outcomes.ts +29 -0
- package/src/task-agent/repair-worker.ts +14 -4
- package/src/task-agent/shadow.ts +48 -4
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +525 -57
- package/src/task-agent/task-parent-role-llm.ts +36 -0
- package/src/task-agent/tier-split.ts +196 -0
- package/src/task-agent/work-order-build.ts +2 -0
- package/src/task-agent/work-order.ts +3 -2
- package/src/task-orchestrator/generator-prompt.ts +60 -11
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +42 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +130 -11
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
- package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
- package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +19 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
- package/src/task-orchestrator/surfaces/pod-provenance.ts +82 -0
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
- package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +7 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +521 -40
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +759 -50
- package/src/task-orchestrator/surfaces/self-implement.ts +65 -2
- package/src/tool-runtime/index.ts +15 -2
- package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
- package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
- package/src/tty-foreground.ts +155 -0
- package/src/tui/README.md +2 -0
- package/src/tui/chat/background-job-status.ts +54 -0
- package/src/tui/chat/goal-interim.ts +173 -0
- package/src/tui/chat/log-view.ts +267 -0
- package/src/tui/chat/self-health-status.ts +232 -0
- package/src/tui/entry.ts +695 -0
- package/src/tui/input/composer.ts +151 -0
- package/src/tui/seat-mail-boot.ts +59 -0
- package/src/tui/slash/compact-command.ts +7 -0
- package/src/tui/slash/compact.ts +94 -0
- package/src/tui/slash/new-session.ts +35 -0
- package/src/tui/slash/resume.ts +75 -0
- package/src/tui-client/local-chat-turn.ts +282 -0
- package/src/tui-client/local-harness-command.ts +63 -0
- package/src/tui-client/local-provider-command.ts +184 -0
- package/src/tui-client/next-dashboard-runtime.ts +8 -0
- package/src/tui-client/next-loop-command.ts +70 -0
- package/src/tui-client/next-wakeup.ts +191 -0
- package/src/tui.ts +37 -0
- package/src/ui/modal-adapter.ts +2 -2
- package/src/user-config.ts +4 -2
- package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
- package/src/ux-sim/cli-config-audit-device.ts +121 -0
- package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
- package/src/ux-sim/event-monkey.ts +379 -0
- package/src/ux-sim/fake-bin.ts +87 -7
- package/src/ux-sim/fake-herdr.ts +238 -0
- package/src/ux-sim/fake-llm.ts +111 -6
- package/src/ux-sim/fault-glm-no-credential.ts +116 -0
- package/src/ux-sim/fault-nextmove-dispatch.ts +367 -0
- package/src/ux-sim/fault-pod-codex-503.ts +211 -0
- package/src/ux-sim/fault-scope-escape-lift.ts +216 -0
- package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
- package/src/ux-sim/goal-device.ts +619 -0
- package/src/ux-sim/in-process-cli.ts +22 -5
- package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
- package/src/ux-sim/mock-authoring-first-run.ts +97 -0
- package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
- package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
- package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
- package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
- package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
- package/src/ux-sim/onboarding-first-run-device.ts +269 -0
- package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
- package/src/ux-sim/subagent-tools-device.ts +81 -0
- package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
- package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
- package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
- package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
- package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
- package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2987-30cf9c2eb7b8bdd5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
- package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
- package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
- package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
- package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
- package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
- package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
- package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
- package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
- package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +0 -287
- package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
- package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
- package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
- package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
- package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-df3445b207ceceab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
- package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
- /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
package/src/cli/tasks-cli.ts
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { loadRunLedger as loadRunLedgerSync, loadRunLedgerWithMetadata, runLedgerDir, resolveFederatedRunLedgerDirectories, type RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
5
6
|
import { goalRunDbPath, loadGoalRunQuery } from '../self-implement/goal-run-store.js';
|
|
6
7
|
import { loadGoalAskRecords } from '../self-implement/goal-ask-store.js';
|
|
7
8
|
import { dirname, join, resolve } from 'node:path';
|
|
8
9
|
import { InvalidArgumentError, Option, type Command } from 'commander';
|
|
9
10
|
import { debug } from '../debug/log.js';
|
|
10
|
-
import {
|
|
11
|
-
import { readNexusRuntime } from '../nexus/runtime.js';
|
|
11
|
+
import { readRunExits } from '../harness/harness-incidents.js';
|
|
12
12
|
import { checklistDevVersion, listChecklist } from '../release-loop/checklist.js';
|
|
13
13
|
import { assignedVersions, releasedVersion, validateVersion } from '../release-loop/feature-store.js';
|
|
14
14
|
import { compareVersions } from '../directives/version-at.js';
|
|
15
|
-
import {
|
|
16
|
-
import type
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { replayRemoteCardMoves } from '../task-agent/shadow.js';
|
|
22
|
-
import { remoteLedgerMirrorDirs } from '../task-agent/parent-host.js';
|
|
15
|
+
import type { BoardNode } from '../task-agent/board.js';
|
|
16
|
+
import { boundPrTerminal, cardPrNumber, CLOSED_BY_HAND_EVENT, closeTaskCard, COMPLETION_KINDS, defaultDraftClaimGhPorts, handDraftClaim, HAND_MOVE_HISTORY_EVENT, updateTaskCard, resolveHandText, readTaskAgentState, readTaskCard, runTagChange, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_SEATS, type CompletionKind, type DraftClaimGhPorts, type TaskCard, type TaskCardEvent, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
|
|
17
|
+
import { recordWiringLiveVerified, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
|
|
18
|
+
import { remoteLedgerMirrorDir, remoteLedgerMirrorDirs } from '../task-agent/parent-host.js';
|
|
19
|
+
import { buildWorkOrder } from '../task-agent/work-order-build.js';
|
|
20
|
+
import { cellIdFromBody } from '../pr-tool/census.js';
|
|
23
21
|
import { ambiguousGreenSweepPrs, decideGreenSweep, greenSweepCandidates, observeGreenSweep, type GreenSweepPr } from '../task-agent/green-sweep.js';
|
|
24
22
|
import { HAND_MOVE_KINDS, recordHandMove } from '../task-agent/live-move-history.js';
|
|
25
|
-
import
|
|
23
|
+
import type { TierSizedDecompose } from '../task-agent/tier-split.js';
|
|
24
|
+
import type { AdvanceMissionResult, MissionDecompose, PieceEvidenceReader } from '../task-agent/mission.js';
|
|
26
25
|
import { formatSevenVerbLive, formatTaskAgentCover, measureSevenVerbLive, measureTaskAgentCover, type CoverRow, type ImplementCardRunBinding, type ImplementRunMergeEvent, type SevenVerbLive, type TaskAgentCoverInput } from '../task-agent/cover.js';
|
|
26
|
+
import { measureRepairOutcomes } from '../task-agent/repair-outcomes.js';
|
|
27
27
|
import { LogStore, STORE_SAFETY_MAX, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
28
28
|
import { resolveLogTargets } from './logs-cli.js';
|
|
29
|
+
import { createTasksCliContext, handleError, NEXUS_DOWN, NexusUnavailable, registerTaskAgentSink, registerTaskListCommand, registerTaskShowCommand, registerTasksRoot, request, responseJson, TASK_AGENT_SINK_SURFACE } from './tasks-read-cli.js';
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
// CLI-ENTRY-LAZY — 가벼운 읽기 경로(`tasks-read-cli.ts`)로 옮긴 공용 표면은 옛 import 경로 그대로 남긴다.
|
|
32
|
+
export { registerTaskAgentSink, TASK_AGENT_SINK_SURFACE };
|
|
33
|
+
|
|
34
|
+
function headLine(text: string): string {
|
|
35
|
+
const first = text.split('\n')[0]!.trim();
|
|
36
|
+
return first.length > 100 ? `${first.slice(0, 100)}…` : first;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
39
|
export interface TasksCliDeps {
|
|
33
40
|
baseUrl?: string;
|
|
@@ -45,10 +52,14 @@ export interface TasksCliDeps {
|
|
|
45
52
|
taskStatePath?: string;
|
|
46
53
|
/** 조합 선택 시험에서만 그래프 저장소 루트를 바꾼다. */
|
|
47
54
|
taskCompositionRoot?: string;
|
|
55
|
+
/** DRAFT-CLAIM-AUTO-2-FU — `tasks hand --draft-claim <pr>` 의 관측·게시·수확 리뷰 포트(시험 주입). 기본 = `defaultDraftClaimGhPorts()`(gh CLI ⊕ 생산 리뷰 러너). */
|
|
56
|
+
draftClaimGh?: DraftClaimGhPorts;
|
|
48
57
|
/** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `hand`·`show` 가 부른다. */
|
|
49
58
|
registerSink?: (surface: string) => Promise<unknown>;
|
|
50
59
|
/** `task hand --mission` 의 분해기(시험 주입). 기본 = 나열형은 결정론 · 아니면 `decomposeSelfDevGoal`. */
|
|
51
60
|
missionDecompose?: MissionDecompose;
|
|
61
|
+
/** TIER-SPLIT-BEFORE-LAUNCH — `tasks hand --tier 상|특 --live` 의 분해기(시험 주입). 키가 없으면 `DEFAULT_TIER_SIZED_DECOMPOSE`. */
|
|
62
|
+
tierSizedDecompose?: TierSizedDecompose;
|
|
52
63
|
/** `task advance` 의 착지 근거 읽기(시험 주입). 기본 = `predecessorState`(gh · 런 원장). */
|
|
53
64
|
pieceEvidence?: PieceEvidenceReader;
|
|
54
65
|
/** `task board` 의 내부 팩 어댑터(시험 주입). 기본 = 현재 판(`checklistDevVersion()`)의 `releasePackBoard` — 읽기만. */
|
|
@@ -57,6 +68,8 @@ export interface TasksCliDeps {
|
|
|
57
68
|
releaseVersions?: () => string[];
|
|
58
69
|
/** Isolated checklist read for PR adoption. */
|
|
59
70
|
adoptChecklist?: typeof listChecklist;
|
|
71
|
+
/** Isolated checklist read for work-order build. */
|
|
72
|
+
workOrderChecklist?: typeof listChecklist;
|
|
60
73
|
/** `tasks adopt-pr` live PR read seam; default is gh in this repository. */
|
|
61
74
|
adoptPrView?: (number: number, cwd: string) => import('../task-agent/adopt-pr.js').AdoptPrView;
|
|
62
75
|
/** Isolated ledger proof for a launched card being adopted. */
|
|
@@ -85,6 +98,19 @@ export interface TasksCliDeps {
|
|
|
85
98
|
coverImplementRunMerges?: () => ReadonlyMap<string, ImplementRunMergeEvent> | null;
|
|
86
99
|
/** Read test-universe ledgers only when --include-test was requested. */
|
|
87
100
|
coverIncludeTest?: boolean;
|
|
101
|
+
/** `tasks outcomes` 의 «지금» — 상대 기간(`--since 24h`)을 푸는 기준(시험 주입). 기본 = `Date.now()`. */
|
|
102
|
+
outcomesNow?: () => number;
|
|
103
|
+
/** `tasks outcomes` 페이지 상한(시험 주입). 기본 = `OUTCOMES_QUERY_LIMIT`. */
|
|
104
|
+
outcomesLimit?: number;
|
|
105
|
+
/** `tasks doctor` read-only adapters; tests inject L1 evidence without calling gh or the operating ledger. */
|
|
106
|
+
doctorVersion?: () => string;
|
|
107
|
+
doctorChecklist?: typeof listChecklist;
|
|
108
|
+
doctorRun?: (command: string, args: string[]) => { status: number | null; stdout: string };
|
|
109
|
+
/** `tasks hand --live --checklist` checks open PRs before creating another implementation run. */
|
|
110
|
+
handOpenPrRun?: (command: string, args: string[]) => { status: number | null; stdout: string };
|
|
111
|
+
/** Repository selected for the open-PR census; the same origin is used by PR adoption. */
|
|
112
|
+
handOriginRepo?: () => string;
|
|
113
|
+
doctorLedger?: (runId: string, statePath: string) => import('../self-implement/run-ledger.js').RunLedgerEntry[] | null;
|
|
88
114
|
/** TA-REJUDGE-ON-HEAD — `tasks rejudge` 의 판단 의존(시험 주입 · 기본 = 설정 liveMoves ⊕ 실물 gh·리뷰·재게이트·land). */
|
|
89
115
|
rejudge?: Partial<import('../task-agent/rejudge-on-head.js').RejudgeDeps>;
|
|
90
116
|
/** TA-REJUDGE-ON-HEAD — `tasks regate-run` 의 재게이트(시험 주입 · 기본 `runHostRegate`). */
|
|
@@ -111,21 +137,6 @@ function advanceLines(result: AdvanceMissionResult): string[] {
|
|
|
111
137
|
];
|
|
112
138
|
}
|
|
113
139
|
|
|
114
|
-
/** `tasks hand`·`show` 가 남기는 `task-agent` 관측의 logs.db surface 라벨. */
|
|
115
|
-
export const TASK_AGENT_SINK_SURFACE = 'task-agent';
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 독립 CLI 프로세스는 데몬의 logs.db 싱크를 상속하지 않는다 — 등록하지 않으면 `debug.log('task-agent', …)` 가
|
|
119
|
-
* 파일 트레일에만 남고 `elanous logs --category task-agent` 에 0건으로 보인다(10-06 실측: 카드 3장 · 이벤트 0).
|
|
120
|
-
* fail-open — 등록 실패가 과제 넘기기를 막지 않는다.
|
|
121
|
-
*/
|
|
122
|
-
export async function registerTaskAgentSink(register?: (surface: string) => Promise<unknown>): Promise<void> {
|
|
123
|
-
try {
|
|
124
|
-
const fn = register ?? (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink;
|
|
125
|
-
await fn(TASK_AGENT_SINK_SURFACE);
|
|
126
|
-
} catch { /* fail-open — 파일 트레일이 진실원 */ }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
140
|
/**
|
|
130
141
|
* 기본 발사기 — 지금 도는 진입점으로 `--config-dir <운영 config>` 를 붙여 떼어 띄운다.
|
|
131
142
|
* config-dir 은 명시(`--config-dir`/`--test`)가 있으면 그것, 없으면 운영 루트(`prodInstanceRoot()`)다.
|
|
@@ -163,10 +174,17 @@ export interface DefaultTaskLauncherDeps {
|
|
|
163
174
|
/** HQ 판(시험 주입). */
|
|
164
175
|
localVersion?: string;
|
|
165
176
|
/** HQ-owned, refresh-less Codex access mirror to install before a remote parent launch. */
|
|
166
|
-
codexAccessMirror?: () => Promise<{ elanousAuth: string; codexAuth: string; account?: string; usedPercent?: number; thresholdPercent?: number } | null>;
|
|
177
|
+
codexAccessMirror?: () => Promise<{ elanousAuth: string; codexAuth: string; account?: string; usedPercent?: number; thresholdPercent?: number; mirrors?: Array<{ elanousAuth: string; codexAuth: string; account: string; usedPercent?: number; thresholdPercent: number; expiresAt: number }> } | null>;
|
|
178
|
+
/** HQ-owned Grok subscription access mirror; resolved before the first remote SSH call. */
|
|
179
|
+
grokAccessMirror?: () => import('../task-agent/parent-host.js').OwnerGrokAccessMirror;
|
|
180
|
+
/** Clock used to recheck mirror freshness immediately before installation. */
|
|
181
|
+
now?: () => number;
|
|
167
182
|
/** Isolated integration seam; production still reads the HQ rotation snapshot and config directly. */
|
|
168
183
|
ownerCodexRotation?: () => ReturnType<typeof import('../oauth/codex-account-store.js').inspectCodexRotation>;
|
|
169
|
-
ownerCodexRotationConfig?: { accountOrder?: readonly string[]; thresholdPercentByAccount?: Readonly<Record<string, unknown
|
|
184
|
+
ownerCodexRotationConfig?: { accountOrder?: readonly string[]; thresholdPercentByAccount?: Readonly<Record<string, unknown>>;
|
|
185
|
+
creditsAllowed?: boolean; creditReserve?: number };
|
|
186
|
+
/** The HQ owner fence is checked before any token refresh. */
|
|
187
|
+
ownerCodexLeaseHolder?: () => boolean;
|
|
170
188
|
/** 원격 → 로컬 되돌림 알림(시험 주입). 기본 = stderr 한 줄. */
|
|
171
189
|
notice?: (line: string) => void;
|
|
172
190
|
}
|
|
@@ -187,13 +205,17 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
187
205
|
const multi = Array.isArray(configured);
|
|
188
206
|
const targets = configured === null ? [] : parentHostModule.resolveParentHosts(configured, deps.parentHost === undefined ? process.env : { ...process.env, ELANOUS_TA_PARENT_HOST: undefined });
|
|
189
207
|
if (!targets.length) return null;
|
|
208
|
+
const providerFromArgs = args.find((arg) => arg.startsWith('--child-llm-provider='))?.slice('--child-llm-provider='.length)
|
|
209
|
+
?? (args.includes('--child-llm-provider') ? args[args.indexOf('--child-llm-provider') + 1] : undefined);
|
|
210
|
+
const effectiveChildProvider = context.effectiveChildProvider ?? providerFromArgs;
|
|
190
211
|
const notice = deps.notice ?? ((line: string) => { try { process.stderr.write(`${line}\n`); } catch { /* ignore */ } });
|
|
191
212
|
const failed = (target: typeof targets[number], gaps: string[], facts: Record<string, string> = {}): void => {
|
|
192
213
|
try { debug.log('task-agent', 'parent-host-fallback', { host: target.host, runId: context.runId, gaps, facts }); } catch { /* fail-soft */ }
|
|
193
214
|
notice(`[parent-host] ${target.host} 로 못 띄움 — ${multi ? '다음 호스트 또는 로컬 발사로 간다' : '로컬 발사로 간다'}: ${gaps.join(' · ')}`);
|
|
194
215
|
};
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
type OwnerMirror = { elanousAuth: string; codexAuth: string; account?: string; usedPercent?: number; thresholdPercent?: number; mirrors?: Array<{ elanousAuth: string; codexAuth: string; account: string; usedPercent?: number; thresholdPercent: number; expiresAt: number }> };
|
|
217
|
+
let accessMirror: OwnerMirror | null | undefined;
|
|
218
|
+
const getAccessMirror = async (): Promise<OwnerMirror | null> => {
|
|
197
219
|
if (accessMirror !== undefined) return accessMirror;
|
|
198
220
|
if (deps.codexAccessMirror) {
|
|
199
221
|
accessMirror = await deps.codexAccessMirror();
|
|
@@ -205,37 +227,66 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
205
227
|
import('../oauth/codex.js'),
|
|
206
228
|
import('../hq/hq.js'),
|
|
207
229
|
]);
|
|
208
|
-
const { inspectCodexRotation } = await import('../oauth/codex-account-store.js');
|
|
230
|
+
const { inspectCodexRotation, codexQuotaPolicyFromConfig } = await import('../oauth/codex-account-store.js');
|
|
231
|
+
const { codexPolicyAllowsCredits } = await import('../oauth/codex-quota-policy.js');
|
|
209
232
|
const { planOwnerCodexMirrorCandidates, podAccountThresholdPercent } = await import('../task-orchestrator/surfaces/pod-account-broker.js');
|
|
210
233
|
const { getUserConfig } = await import('../user-config.js');
|
|
211
234
|
const rotation = deps.ownerCodexRotation?.() ?? inspectCodexRotation();
|
|
212
235
|
const config = getUserConfig();
|
|
213
236
|
const ownerConfig = deps.ownerCodexRotationConfig;
|
|
214
237
|
const candidates = [...rotation.candidates];
|
|
238
|
+
// No local credential preserves the established local-launch fallback.
|
|
239
|
+
if (candidates.length === 0) { accessMirror = null; return null; }
|
|
240
|
+
const ownerLeaseHeld = deps.ownerCodexLeaseHolder?.() ?? hq.hqFenceDecision('ledger-cli').run;
|
|
241
|
+
if (!ownerLeaseHeld) throw new NoEligibleOwnerCodexAccountError('HQ Codex 토큰 주인 임대가 없어 원격 미러 발사를 거부한다');
|
|
215
242
|
const eligible = planOwnerCodexMirrorCandidates(candidates, {
|
|
216
243
|
accountOrder: ownerConfig?.accountOrder ?? config.llm?.codexAccountOrder ?? [],
|
|
217
244
|
thresholdPercentByAccount: ownerConfig?.thresholdPercentByAccount ?? config.llm?.codexAccountRotationThresholdPercentByAccount,
|
|
245
|
+
creditsAllowed: ownerConfig?.creditsAllowed ?? codexPolicyAllowsCredits(codexQuotaPolicyFromConfig()),
|
|
246
|
+
creditReserve: ownerConfig?.creditReserve ?? config.llm?.codexCreditReserve,
|
|
218
247
|
});
|
|
219
248
|
if (candidates.length > 0 && eligible.length === 0) throw new NoEligibleOwnerCodexAccountError('HQ 소유 Codex 계정이 모두 사용 임계에 도달해 원격 부모 발사를 거부한다');
|
|
220
249
|
const prerefreshDeps = {
|
|
221
250
|
credentials: (account: string) => pod.hostCredentials(account, authStore.authStorePath(), () => '', { requireExplicitChatgptAuthMode: true }),
|
|
222
251
|
refresh: (account: string) => codex.refreshCodexAccountHome(account),
|
|
223
|
-
isLeaseHolder: () => hq.hqFenceDecision('ledger-cli').run,
|
|
252
|
+
isLeaseHolder: () => deps.ownerCodexLeaseHolder?.() ?? hq.hqFenceDecision('ledger-cli').run,
|
|
224
253
|
};
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
const mirrors: NonNullable<OwnerMirror['mirrors']> = [];
|
|
255
|
+
for (const [index, candidate] of eligible.entries()) {
|
|
256
|
+
const usedPercent = candidate.usedPercent;
|
|
257
|
+
const thresholdPercent = podAccountThresholdPercent(candidate.name, {
|
|
258
|
+
thresholdPercentByAccount: ownerConfig?.thresholdPercentByAccount ?? config.llm?.codexAccountRotationThresholdPercentByAccount,
|
|
259
|
+
});
|
|
260
|
+
// HQ is the sole refresh owner. Send every usable account as an access-only snapshot
|
|
261
|
+
// so the remote Pod broker can rotate instead of pinning all jobs to one account.
|
|
227
262
|
let credential: Awaited<ReturnType<typeof pod.podCodexCredentialWithPrerefresh>> = null;
|
|
228
263
|
try { credential = await pod.podCodexCredentialWithPrerefresh(candidate.name, prerefreshDeps); }
|
|
229
|
-
catch {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
264
|
+
catch {
|
|
265
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-account-skipped', { account: candidate.name, reason: 'credential-error' }); } catch { /* fail-soft */ }
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!credential) {
|
|
269
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-account-skipped', { account: candidate.name, reason: 'expiring-or-refresh-failed' }); } catch { /* fail-soft */ }
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const expiresAt = (JSON.parse(credential.elanousAuth) as { providers?: { 'openai-codex'?: { tokens?: { expiresAt?: unknown } } } }).providers?.['openai-codex']?.tokens?.expiresAt;
|
|
273
|
+
if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt) || expiresAt <= (deps.now?.() ?? Date.now()) + 3 * 60 * 60 * 1000) {
|
|
274
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-account-skipped', { account: candidate.name, reason: 'access-expiring' }); } catch { /* fail-soft */ }
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
mirrors.push({ elanousAuth: credential.elanousAuth, codexAuth: credential.codexAuth, account: candidate.name,
|
|
278
|
+
expiresAt,
|
|
279
|
+
...(usedPercent !== undefined ? { usedPercent } : {}), thresholdPercent });
|
|
280
|
+
// The receiving marker parser accepts at most 16 distinct accounts.
|
|
281
|
+
// Apply the same bound before writing credentials on the remote parent.
|
|
282
|
+
if (mirrors.length === 16) {
|
|
283
|
+
for (const skipped of eligible.slice(index + 1)) {
|
|
284
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-account-skipped', { account: skipped.name, reason: 'snapshot-limit-16' }); } catch { /* fail-soft */ }
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
237
288
|
}
|
|
238
|
-
accessMirror = null;
|
|
289
|
+
accessMirror = mirrors.length ? { ...mirrors[0]!, mirrors } : null;
|
|
239
290
|
return accessMirror;
|
|
240
291
|
};
|
|
241
292
|
if (cwd) {
|
|
@@ -248,6 +299,11 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
248
299
|
// 그 앞의 readParentHostCapacity 도 ssh). 가드를 아래로 내리면 hold 경로에서 시험이 실 ssh 를 쏜다(R7 사고 모양).
|
|
249
300
|
// hold 경로를 재는 시험은 deps.ssh 가짜 또는 parentHost:null 을 주입한다(real-launch-guard.test 가 이 순서를 문다).
|
|
250
301
|
if (!deps.ssh) (await import('../task-agent/real-launch-guard.js')).refuseRealLaunchUnderTest('remote-parent-ssh', targets.map((target) => target.host).join(','));
|
|
302
|
+
// Grok access is selected/refreshed on HQ before preflight performs any SSH call. A missing or stale
|
|
303
|
+
// subscription must fail closed without touching the remote host or falling back to a local launch.
|
|
304
|
+
const grokAccessMirror = effectiveChildProvider === 'grok'
|
|
305
|
+
? (deps.grokAccessMirror?.() ?? parentHostModule.resolveOwnerGrokAccessMirror())
|
|
306
|
+
: undefined;
|
|
251
307
|
let launchHoldActive = false;
|
|
252
308
|
try {
|
|
253
309
|
if (deps.launchHoldActive) launchHoldActive = deps.launchHoldActive();
|
|
@@ -264,6 +320,8 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
264
320
|
delete remoteEnv.ELANOUS_REMOTE_CODEX_ACCOUNT;
|
|
265
321
|
delete remoteEnv.ELANOUS_REMOTE_CODEX_USED_PERCENT;
|
|
266
322
|
delete remoteEnv.ELANOUS_REMOTE_CODEX_THRESHOLD_PERCENT;
|
|
323
|
+
delete remoteEnv.ELANOUS_REMOTE_CODEX_ACCOUNTS;
|
|
324
|
+
delete remoteEnv.ELANOUS_REMOTE_CODEX_ACCESS_ONLY;
|
|
267
325
|
if (podGoal) for (const key of ['ELANOUS_POD_MEMORY_TIER', 'ELANOUS_POD_MEMORY', 'ELANOUS_POD_MEMORY_HIGH', 'ELANOUS_POD_MEMORY_LITE', 'ELANOUS_POD_GOAL_TYPE'] as const) {
|
|
268
326
|
const value = context.env[key] ?? process.env[key];
|
|
269
327
|
if (value !== undefined) remoteEnv[key] = value;
|
|
@@ -300,30 +358,63 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
300
358
|
if (preflight.warnings.length) notice(`⚠ ${target.host} 자격: ${preflight.warnings.join(' · ')}`);
|
|
301
359
|
try {
|
|
302
360
|
if (parentHostModule.shouldInstallRemoteCodexAccess(context.effectiveChildProvider)) {
|
|
303
|
-
let mirror:
|
|
361
|
+
let mirror: OwnerMirror | null = null;
|
|
304
362
|
let mirrorReason = 'credential-source-empty';
|
|
305
363
|
try { mirror = await getAccessMirror(); }
|
|
306
364
|
catch (error) {
|
|
307
365
|
if (error instanceof NoEligibleOwnerCodexAccountError) throw error;
|
|
308
366
|
mirrorReason = 'credential-source-error';
|
|
309
367
|
}
|
|
310
|
-
|
|
368
|
+
// The receiver accepts at most 16 accounts. Use this same bounded
|
|
369
|
+
// snapshot for installation, the launch marker, and the selected account,
|
|
370
|
+
// including mirrors supplied by the injected credential source.
|
|
371
|
+
const mirrors = mirror?.mirrors?.slice(0, 16);
|
|
372
|
+
const selected = mirrors?.[0] ?? mirror;
|
|
373
|
+
if (mirror) {
|
|
374
|
+
// Snapshot construction can outlive the HQ owner lease. Fresh tokens
|
|
375
|
+
// bypass prerefresh's lock, so recheck before any credential leaves HQ.
|
|
376
|
+
if (!(deps.ownerCodexLeaseHolder?.() ?? (await import('../hq/hq.js')).hqFenceDecision('ledger-cli').run)) {
|
|
377
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-owner-lease-lost', { host: target.host, runId: context.runId, reason: 'owner-lease-lost-before-install' }); } catch { /* fail-soft */ }
|
|
378
|
+
throw new NoEligibleOwnerCodexAccountError('HQ Codex 토큰 주인 임대가 설치 직전에 사라져 원격 미러 발사를 거부한다');
|
|
379
|
+
}
|
|
380
|
+
if (mirror.mirrors && mirror.mirrors.length > 16) {
|
|
381
|
+
for (const skipped of mirror.mirrors.slice(16)) {
|
|
382
|
+
try { debug.log('task-agent.parent-host', 'codex-access-mirror-account-skipped', { account: skipped.account, reason: 'snapshot-limit-16' }); } catch { /* fail-soft */ }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Each detached parent keeps its own auth store and Codex homes for
|
|
386
|
+
// its full lifetime, including the legacy single-account source.
|
|
387
|
+
const identity = createHash('sha256').update(`${context.runId}\0${context.launchId}`).digest('hex').slice(0, 20);
|
|
388
|
+
const xdgRoot = `${target.configDir ?? '$HOME/.elanous'}/remote-parent-credentials/${identity}`;
|
|
389
|
+
const mirrorDir = `${xdgRoot}/elanous`;
|
|
390
|
+
const installed = mirrors ?? [{ ...mirror, account: mirror.account ?? 'default' }];
|
|
391
|
+
parentHostModule.installRemoteCodexAccessBatch({ ...target, configDir: mirrorDir }, installed, deps.ssh);
|
|
392
|
+
remoteEnv.XDG_CONFIG_HOME = xdgRoot;
|
|
393
|
+
remoteEnv.CODEX_HOME = `${mirrorDir}/codex-default`;
|
|
394
|
+
// XDG isolation is for Codex credentials only. gh otherwise loses
|
|
395
|
+
// the remote host's existing login before a Pod can be created.
|
|
396
|
+
remoteEnv.GH_CONFIG_DIR = '$HOME/.config/gh';
|
|
397
|
+
}
|
|
311
398
|
else {
|
|
312
399
|
try { debug.log('task-agent.parent-host', 'codex-access-mirror-unavailable', { host: target.host, runId: context.runId, reason: mirrorReason }); } catch { /* fail-soft */ }
|
|
313
400
|
failed(target, ['codex-access-mirror-unavailable']);
|
|
314
401
|
return null;
|
|
315
402
|
}
|
|
316
|
-
if (
|
|
317
|
-
remoteEnv.ELANOUS_REMOTE_CODEX_ACCOUNT =
|
|
318
|
-
remoteEnv.ELANOUS_REMOTE_CODEX_USED_PERCENT =
|
|
319
|
-
if (
|
|
403
|
+
if (selected?.account) {
|
|
404
|
+
remoteEnv.ELANOUS_REMOTE_CODEX_ACCOUNT = selected.account;
|
|
405
|
+
remoteEnv.ELANOUS_REMOTE_CODEX_USED_PERCENT = selected.usedPercent === undefined ? 'unknown' : String(selected.usedPercent);
|
|
406
|
+
if (selected.thresholdPercent !== undefined) remoteEnv.ELANOUS_REMOTE_CODEX_THRESHOLD_PERCENT = String(selected.thresholdPercent);
|
|
320
407
|
}
|
|
408
|
+
if (mirrors) remoteEnv.ELANOUS_REMOTE_CODEX_ACCOUNTS = JSON.stringify(mirrors.map(({ account, usedPercent, thresholdPercent, expiresAt }) => ({ account, usedPercent: usedPercent ?? null, thresholdPercent, expiresAt })));
|
|
409
|
+
remoteEnv.ELANOUS_REMOTE_CODEX_ACCESS_ONLY = '1';
|
|
321
410
|
}
|
|
322
|
-
const launched = parentHostModule.launchRemoteParent(target, args, remoteEnv, deps.ssh
|
|
411
|
+
const launched = parentHostModule.launchRemoteParent(target, args, remoteEnv, deps.ssh,
|
|
412
|
+
grokAccessMirror ? () => grokAccessMirror : undefined, effectiveChildProvider, deps.now);
|
|
323
413
|
try { debug.log('task-agent', 'parent-host-launched', { host: target.host, runId: context.runId, pid: launched.pid, log: launched.log }); } catch { /* fail-soft */ }
|
|
324
414
|
return { runId: context.runId, parentHost: { host: target.host, ...(target.configDir ? { configDir: target.configDir } : {}), pid: launched.pid, log: launched.log } };
|
|
325
415
|
} catch (error) {
|
|
326
416
|
if (error instanceof parentHostModule.RemoteParentNotLaunchedError) { failed(target, [error.message]); continue; }
|
|
417
|
+
if (error instanceof NoEligibleOwnerCodexAccountError) throw error;
|
|
327
418
|
try { debug.log('task-agent', 'parent-host-launch-uncertain', { host: target.host, runId: context.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
328
419
|
throw error;
|
|
329
420
|
}
|
|
@@ -344,6 +435,7 @@ function firstStderrLine(path: string): string {
|
|
|
344
435
|
}
|
|
345
436
|
|
|
346
437
|
export async function defaultTaskLauncher(args: string[], cwd?: string, context?: TaskLaunchContext, deps: DefaultTaskLauncherDeps = {}): Promise<void | TaskLaunchReceipt> {
|
|
438
|
+
if (context) context = (await import('../harness/launch-utterance-ledger.js')).withHandLaunchPairContext(args, context); // LAUNCH-UTTERANCE-LEDGER hand→say pair env
|
|
347
439
|
if (context) {
|
|
348
440
|
const remote = await tryRemoteParent(args, cwd, context, deps);
|
|
349
441
|
if (remote) return remote;
|
|
@@ -446,159 +538,6 @@ export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string,
|
|
|
446
538
|
});
|
|
447
539
|
}
|
|
448
540
|
|
|
449
|
-
/** HARNESS-PARENT-ON-MSB1 — 카드가 원격 부모면 그 런 원장(⊕ Pod 자식)을 HQ 거울로 당긴다. 실패는 관측만(«없음»으로 읽히지 않게). */
|
|
450
|
-
async function mirrorRemoteCardLedger(cardId: string, deps: TasksCliDeps): Promise<void> {
|
|
451
|
-
let card: TaskCard | undefined;
|
|
452
|
-
try { card = readTaskCard(cardId, deps.taskStatePath); } catch { return; }
|
|
453
|
-
if (!card?.parentHost || !card.runId) return;
|
|
454
|
-
try {
|
|
455
|
-
if (deps.remoteLedgerMirror) { deps.remoteLedgerMirror(card); return; }
|
|
456
|
-
const { mirrorRemoteRunLedgers } = await import('../task-agent/parent-host.js');
|
|
457
|
-
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
458
|
-
mirrorRemoteRunLedgers(card.parentHost, card.runId, effectiveInstanceRoot(), deps.cardRunEvidence?.ssh);
|
|
459
|
-
} catch (error) {
|
|
460
|
-
try { debug.log('task-agent', 'remote-ledger-mirror-failed', { card: cardId, host: card.parentHost.host, runId: card.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function moveLine(card: TaskCard): string {
|
|
465
|
-
const move = nextMoveFor(card);
|
|
466
|
-
return `${move.kind}${move.command ? ` — elanous ${move.command.map(shellQuote).join(' ')}` : ''} (${move.reason})`;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function headLine(text: string): string {
|
|
470
|
-
const first = text.split('\n')[0]!.trim();
|
|
471
|
-
return first.length > 100 ? `${first.slice(0, 100)}…` : first;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/** 런 연결 한 줄 — 런 id 가 없으면 없다(옛 카드 · shadow). */
|
|
475
|
-
function runLinkLine(card: TaskCard): string[] {
|
|
476
|
-
if (!card.runId) return [];
|
|
477
|
-
const number = cardPrNumber(card);
|
|
478
|
-
const url = typeof card.pr === 'object' ? card.pr.url : undefined;
|
|
479
|
-
const pr = number !== undefined ? `#${number}${url ? ` ${url}` : ''}` : '-';
|
|
480
|
-
return [`런: ${card.runId} · 골: ${card.goalId ?? '-'} · PR: ${pr}${card.runChildId ? ` · 자식 런: ${card.runChildId}` : ''}`];
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/** RUN-IDENTITY-TAGS — 신분 한 줄 + Pod 라벨 한 줄(원장 정본 2줄 렌더 · 신분 없는 카드는 «미기록»으로 적고 기본값 정책을 밝힌다). */
|
|
484
|
-
function cardIdentityLines(card: TaskCard): string[] {
|
|
485
|
-
const identity = card.identity;
|
|
486
|
-
const { labels } = taskCardLabelContext({ id: card.id });
|
|
487
|
-
// 경계: Pod 에 실제로 붙는 라벨은 카드 id 하나뿐이다 — 신분 값은 Pod 라벨에 아직 안 실린다(RUN-IDENTITY-ADMIT 몫). 화면도 그렇게 말한다.
|
|
488
|
-
const podLine = `Pod 라벨 ${Object.entries(labels).map(([key, value]) => `${key}=${value}`).join(' ')} (신분 값은 Pod 라벨 미탑재 — RUN-IDENTITY-ADMIT)`;
|
|
489
|
-
if (!identity) return [`신분 미기록 — 원장에 identity 없음 · 승인·선점은 기본 class=${runPriorityClass(identity)} 로 읽는다`, podLine];
|
|
490
|
-
// ⛔ 모르는 칸을 기본값으로 보이지 않는다 — 원장에 없는 칸은 «미기록»으로 적는다(확인된 칸만 값으로).
|
|
491
|
-
const identityText = RUN_TAG_KEYS.map((key) => `${key}=${runIdentityFieldText(identity, key)}`).join(' · ');
|
|
492
|
-
const originParts = [identity.origin?.checklistId, identity.origin?.version, identity.origin?.seat, identity.origin?.launcher].filter((part) => part !== undefined);
|
|
493
|
-
return [
|
|
494
|
-
`신분 ${identityText}${originParts.length > 0 ? ` — 출생 ${originParts.join(' · ')}` : ''}`,
|
|
495
|
-
podLine,
|
|
496
|
-
];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function taskCardLines(card: TaskCard): string[] {
|
|
500
|
-
return [
|
|
501
|
-
[card.id, card.status, card.seat ?? '-', card.checklistId ?? '-', card.createdAt, card.text].join('\t'),
|
|
502
|
-
...cardIdentityLines(card),
|
|
503
|
-
...(card.targetPaths !== undefined ? [`대상 경로: ${card.targetPaths.length ? card.targetPaths.join(' · ') : '(유효한 경로 없음)'}`] : []),
|
|
504
|
-
...(card.mission ? [`미션: ${card.mission} · after: ${card.after?.length ? card.after.join(', ') : '-'}`] : []),
|
|
505
|
-
...runLinkLine(card),
|
|
506
|
-
...liveMoveLines(card),
|
|
507
|
-
`다음 수: ${moveLine(card)}`,
|
|
508
|
-
];
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/** TA-LIVE-MOVE-CARD-HISTORY — 카드 history 의 live-move 줄(실행·그림자)을 한 줄씩. 없으면 없다(옛 카드 그대로). */
|
|
512
|
-
function liveMoveLines(card: TaskCard): string[] {
|
|
513
|
-
return (card.history ?? []).filter((item) => item.event === LIVE_MOVE_HISTORY_EVENT || item.event === LIVE_MOVE_RESULT_EVENT).map((item) => item.event === LIVE_MOVE_RESULT_EVENT ? [
|
|
514
|
-
// TA-REVIEW-RESULT-CAPTURE — 회수한 리뷰 결과(또는 «결과 없음») 줄.
|
|
515
|
-
`결과: ${item.at}`, `${item.kind ?? '?'}`, `PR ${item.pr !== undefined ? `#${item.pr}` : '-'}${item.head ? ` head ${item.head.slice(0, 12)}` : ''}`,
|
|
516
|
-
`verdict=${item.verdict ?? '—'} reviewed=${item.reviewed ?? '—'} must-fix=${item.mustFix ?? '—'} route=${item.reviewRoute ?? '—'}`,
|
|
517
|
-
`효과: ${item.effect ?? 'unknown'}`, (item.detail ?? '').replace(/\r\n|\r|\n/g, (match) => (match === '\r' ? '\\r' : '\\n')),
|
|
518
|
-
].join(' · ') : [
|
|
519
|
-
`수: ${item.at}`, `${item.kind ?? '?'} ${item.executorResult ?? '?'}`, `executed=${item.executed === true} ok=${item.ok === undefined ? '—' : item.ok}`,
|
|
520
|
-
`PR ${item.pr !== undefined ? `#${item.pr}` : '-'}${item.head ? ` head ${item.head.slice(0, 12)}` : ''}`,
|
|
521
|
-
`효과: ${item.effect ?? 'unknown'}`, (item.detail ?? '').replace(/\r\n|\r|\n/g, (match) => (match === '\r' ? '\\r' : '\\n')),
|
|
522
|
-
].join(' · '));
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/** 미션 카드 — 머리 한 줄 ⊕ 조각 한 줄씩(상태·다음 수·선행) ⊕ 간선 한 줄씩. */
|
|
526
|
-
function missionLines(mission: TaskCard, pieces: TaskCard[]): string[] {
|
|
527
|
-
const edges = pieces.flatMap((piece) => (piece.after ?? []).map((before) => `간선: ${before} -> ${piece.id}`));
|
|
528
|
-
return [
|
|
529
|
-
[mission.id, 'mission', mission.seat ?? '-', mission.checklistId ?? '-', mission.createdAt, `조각 ${pieces.length} · 간선 ${edges.length} · 출처 ${mission.splitSource ?? '-'}`, headLine(mission.text)].join('\t'),
|
|
530
|
-
...pieces.map((piece) => [`조각: ${piece.id}`, piece.status, nextMoveFor(piece).kind, `after: ${piece.after?.length ? piece.after.join(',') : '-'}`, headLine(piece.text),
|
|
531
|
-
...(piece.runId ? [`런: ${piece.runId}${cardPrNumber(piece) !== undefined ? ` · PR #${cardPrNumber(piece)}` : ''}`] : [])].join('\t')),
|
|
532
|
-
...edges,
|
|
533
|
-
...(mission.greenProposal ? [`green 제안: ${mission.greenProposal.at} · 칸 ${mission.greenProposal.checklistId ?? '-'} · 근거 ${Object.entries(mission.greenProposal.evidence).map(([id, ref]) => `${id}=${ref}`).join(', ')}`] : []),
|
|
534
|
-
];
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const PRIORITY_RANK: Record<Task['priority'], number> = { urgent: 0, high: 1, medium: 2, low: 3 };
|
|
538
|
-
const NEXUS_DOWN = '넥서스가 안 떠 있다 — `elanous nexus run`';
|
|
539
|
-
|
|
540
|
-
function taskOrder(a: TaskRow, b: TaskRow): number {
|
|
541
|
-
return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority]
|
|
542
|
-
|| a.createdAt - b.createdAt || a.id.localeCompare(b.id);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function taskSource(task: TaskRow): string {
|
|
546
|
-
return task.generatedBy?.kind === 'external'
|
|
547
|
-
? `${task.generatedBy.provider}:${task.generatedBy.ref}` : '-';
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function taskLine(task: TaskRow): string {
|
|
551
|
-
return [task.id, task.priority, task.status, task.approval?.state ?? '-', taskSource(task), task.title].join('\t');
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function credentials(deps: TasksCliDeps): { baseUrl: string; token?: string } | null {
|
|
555
|
-
const port = deps.baseUrl !== undefined ? undefined : readNexusRuntime()?.httpPort;
|
|
556
|
-
const baseUrl = deps.baseUrl ?? (port ? `http://127.0.0.1:${port}` : undefined);
|
|
557
|
-
if (!baseUrl) return null;
|
|
558
|
-
// Same as `connector linear sync`: an isolated (--test) universe has no acp-token and its
|
|
559
|
-
// daemon accepts loopback callers without one — send the bearer only when there is one.
|
|
560
|
-
let token = deps.bearerToken;
|
|
561
|
-
if (token === undefined) {
|
|
562
|
-
try { token = readFileSync(join(getElanousConfigDir(), 'acp-token'), 'utf8').trim() || undefined; }
|
|
563
|
-
catch { token = undefined; }
|
|
564
|
-
}
|
|
565
|
-
return { baseUrl, ...(token ? { token } : {}) };
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
class NexusUnavailable extends Error {}
|
|
569
|
-
|
|
570
|
-
async function request(deps: TasksCliDeps, auth: { baseUrl: string; token?: string }, path: string, method = 'GET'): Promise<Response> {
|
|
571
|
-
try {
|
|
572
|
-
return await (deps.fetch ?? fetch)(`${auth.baseUrl}${path}`, {
|
|
573
|
-
method,
|
|
574
|
-
headers: auth.token ? { Authorization: `Bearer ${auth.token}` } : {},
|
|
575
|
-
});
|
|
576
|
-
} catch {
|
|
577
|
-
// Fetch exceptions can include the URL or credentials; never forward their text.
|
|
578
|
-
throw new NexusUnavailable();
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
async function responseJson(response: Response): Promise<unknown> {
|
|
583
|
-
try { return await response.json(); }
|
|
584
|
-
catch { throw new Error('넥서스 응답 JSON이 유효하지 않다'); }
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function httpError(response: Response): Error {
|
|
588
|
-
// Do not echo server-supplied error bodies: they may contain credentials.
|
|
589
|
-
return new Error(`HTTP ${response.status}`);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function handleError(error: unknown, out: (line: string) => void): void {
|
|
593
|
-
if (error instanceof NexusUnavailable) {
|
|
594
|
-
out(NEXUS_DOWN);
|
|
595
|
-
process.exitCode = 2;
|
|
596
|
-
} else {
|
|
597
|
-
out(error instanceof Error ? error.message : '태스크 요청 실패');
|
|
598
|
-
process.exitCode = 1;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
541
|
const COVER_QUERY_LIMIT = STORE_SAFETY_MAX;
|
|
603
542
|
|
|
604
543
|
/** Federated read: rows from every store, whether any single store hit the bound, and how many stores could not be read. */
|
|
@@ -897,17 +836,736 @@ export function readTaskAgentCover(
|
|
|
897
836
|
limitReached: truncated.taskAgentActions || truncated.allLands || moveRead?.limitReached === true || orderRead?.limitReached === true || goalAsksTruncated || goalRunsTruncated, unreadableStores };
|
|
898
837
|
}
|
|
899
838
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
839
|
+
const OUTCOMES_QUERY_LIMIT = STORE_SAFETY_MAX;
|
|
840
|
+
|
|
841
|
+
/** «끝났다»로 읽는 카드 상태 — 이 밖(handed·launched)은 PR 종결 관측이 있어야 끝난 것으로 센다. */
|
|
842
|
+
const OUTCOME_TERMINAL_STATUSES: ReadonlySet<TaskCard['status']> = new Set(['failed', 'launch-failed', 'landed', 'landed-by-hand', 'done']);
|
|
843
|
+
|
|
844
|
+
/** 카드 원장에서 «종결»로 읽는 사건 — 이 줄의 시각만 종료 시각으로 쓴다(run-bound · launch 같은 끝나기 전 사건은 종료 시각이 아니다). */
|
|
845
|
+
const OUTCOME_TERMINAL_HISTORY_EVENTS: ReadonlySet<string> = new Set([
|
|
846
|
+
'failed', 'launch-failed', 'closed-by-hand', 'landed-via-train', 'postland-card', 'postland',
|
|
847
|
+
]);
|
|
848
|
+
const OUTCOME_SUCCESS_HISTORY_EVENTS: ReadonlySet<string> = new Set(['landed-via-train', 'postland-card', 'postland']);
|
|
849
|
+
|
|
850
|
+
/** `--since`/`--until` 의 상대 기간 — `24h` · `90m` · `3d`. */
|
|
851
|
+
const OUTCOMES_RELATIVE_WINDOW = /^\d+(?:\.\d+)?(?:m|h|d)$/;
|
|
852
|
+
|
|
853
|
+
/** `--since`/`--until` — ISO 시각 또는 상대 기간. 그 밖의 모양은 창을 지어내지 않고 거부한다. */
|
|
854
|
+
function parseOutcomeTime(value: string | undefined, flag: string, now: number): number | undefined {
|
|
855
|
+
if (value === undefined) return undefined;
|
|
856
|
+
const text = value.trim();
|
|
857
|
+
if (OUTCOMES_RELATIVE_WINDOW.test(text)) {
|
|
858
|
+
const amount = Number(text.slice(0, -1));
|
|
859
|
+
const unit = text.slice(-1);
|
|
860
|
+
const hours = unit === 'd' ? amount * 24 : unit === 'm' ? amount / 60 : amount;
|
|
861
|
+
return now - hours * 3_600_000;
|
|
862
|
+
}
|
|
863
|
+
const time = Date.parse(text);
|
|
864
|
+
if (!/^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d\d:\d\d)$/.test(text) || !Number.isFinite(time)) {
|
|
865
|
+
throw new Error(`${flag} 는 ISO 시각 또는 상대 기간(24h·3d)이어야 한다`);
|
|
866
|
+
}
|
|
867
|
+
return time;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/** `tasks outcomes` 한 줄 — 카드 하나의 결과. 모르는 칸은 null(지어내지 않는다). */
|
|
871
|
+
export interface TaskOutcomeRow {
|
|
872
|
+
/** 칸(체크리스트 id) — 칸 없이 넘긴 카드는 null. */
|
|
873
|
+
cell: string | null;
|
|
874
|
+
card: string;
|
|
875
|
+
runId: string | null;
|
|
876
|
+
/** 사유 원장을 실제로 읽은 자식 런(부모 pod-child-run 매핑). */
|
|
877
|
+
childRunId: string | null;
|
|
878
|
+
/** 부모 호스트 원격 이름 — HQ 로컬 발사면 null. */
|
|
879
|
+
host: string | null;
|
|
880
|
+
/** 이 행의 카드 또는 PR 종결 사건 시각(ISO). */
|
|
881
|
+
endedAt: string;
|
|
882
|
+
cardTerminalAt: string | null;
|
|
883
|
+
/** 카드가 결속 PR의 종단을 관측한 시각. GitHub 원본 mergedAt/closedAt 와 동일하다고 추정하지 않는다. */
|
|
884
|
+
prTerminalAt: string | null;
|
|
885
|
+
/** 이 카드 원장은 GitHub 원본 terminal timestamp 를 보존하지 않는다. */
|
|
886
|
+
ghTerminalAt: null;
|
|
887
|
+
ghTerminalMissing: 'not-stored-in-card';
|
|
888
|
+
/** 이 행이 들어가는 독립 분모. 같은 카드의 카드·PR 종결은 별도 행이다. */
|
|
889
|
+
terminalKind: 'card' | 'pr';
|
|
890
|
+
/** 이 사건을 센 근거 — 카드 상태·이력 사건 또는 결속된 PR 상태를 그대로 적는다. */
|
|
891
|
+
countReason: string;
|
|
892
|
+
/** 원시 카드 상태. board가 PR 종결로 투영한 상태와 구별한다. */
|
|
893
|
+
cardStatus: TaskCard['status'];
|
|
894
|
+
/** PR 결속 — 번호 ⊕ 종결 상태(종결을 못 봤으면 state null). */
|
|
895
|
+
pr: { number: number; state: 'MERGED' | 'CLOSED' | null } | null;
|
|
896
|
+
/** 종결 사유 원문 — 자식 원장의 마지막 pr-open-failed/child-error data.error 꼬리(없으면 null). */
|
|
897
|
+
reason: string | null;
|
|
898
|
+
/** 종결 사유를 읽은 곳(`child-ledger:<event>` · 못 읽었으면 null). */
|
|
899
|
+
reasonFrom: string | null;
|
|
900
|
+
/** 원본 증거 또는 그 증거를 못 실은 이유. */
|
|
901
|
+
evidence: { childLedger: { event: string; error: string } | null; childLedgerMissing: string | null;
|
|
902
|
+
lastLines: string[] | null; lastLinesMissing: string | null };
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export interface TaskOutcomeReport {
|
|
906
|
+
since: string | null;
|
|
907
|
+
until: string | null;
|
|
908
|
+
rows: TaskOutcomeRow[];
|
|
909
|
+
/** 사유별 집계 — 사유 분모는 실패 카드 종단뿐이다(PR/성공 종단은 포함하지 않는다). */
|
|
910
|
+
aggregate: { cards: number; cardTerminals: number; prTerminals: number; failedCardTerminals: number;
|
|
911
|
+
reasons: Array<{ reason: string; count: number }>; missingReason: number };
|
|
912
|
+
/** 조회 상한에 닿아 절단됐나 — 그때 집계는 «이 창 안에서 센 행»만 센다. */
|
|
913
|
+
limitReached: boolean;
|
|
914
|
+
/** 절단 사유 — 카드 조회 상한. */
|
|
915
|
+
stopReason: 'page-limit' | null;
|
|
916
|
+
/** 자식 원장(자식 런 원장 파일)을 실제로 읽었나. */
|
|
917
|
+
childLedgerObserved: boolean;
|
|
918
|
+
/** 종결 사유 원문을 하나라도 관측했나. */
|
|
919
|
+
reasonObserved: boolean;
|
|
920
|
+
/** 끝났는데 종료 시각을 몰라 창에 넣지 못한 카드 수. */
|
|
921
|
+
undatedCards: number;
|
|
922
|
+
/** 시각 없는 카드의 기간 귀속은 계산할 수 없다. undatedCards 는 창 밖까지 포함한 수다. */
|
|
923
|
+
undatedPeriodAttribution: 'unknown';
|
|
924
|
+
/** 두 종결 사건이 모두 창 안에 있는 카드 ID. --by 축 필터에서도 같은 목록을 낸다. */
|
|
925
|
+
overlapCardIds: string[];
|
|
926
|
+
/** 아직 끝나지 않은 카드 수(창 대상 아님). */
|
|
927
|
+
openCards: number;
|
|
928
|
+
warning?: string;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** 마지막 실제 종결 사건. 후보·발사·run-bound·관측 줄은 종료 증거가 아니다. */
|
|
932
|
+
function outcomeTerminalHistoryLine(card: TaskCard): TaskCardEvent | undefined {
|
|
933
|
+
return (card.history ?? []).filter((item) => OUTCOME_TERMINAL_HISTORY_EVENTS.has(item.event)).at(-1);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/** 종결 사유 원문을 주는 자식 원장 이벤트 — 뒤에서부터 마지막 것을 쓴다. */
|
|
937
|
+
const OUTCOME_REASON_LEDGER_EVENTS = new Set(['pr-open-failed', 'child-error']);
|
|
938
|
+
|
|
939
|
+
/** 부모 runId 원장의 마지막 pod-child-run 매핑이 가리키는 자식 런. 매핑이 없으면 null. */
|
|
940
|
+
function outcomeChildRunId(card: TaskCard, ledgerDir: string): string | null {
|
|
941
|
+
if (typeof card.runId !== 'string' || card.runId === '') return null;
|
|
942
|
+
const parent = loadRunLedgerSync(card.runId, ledgerDir);
|
|
943
|
+
if (parent === null) return null;
|
|
944
|
+
for (let i = parent.length - 1; i >= 0; i -= 1) {
|
|
945
|
+
const entry = parent[i]!;
|
|
946
|
+
if (entry.event !== 'pod-child-run') continue;
|
|
947
|
+
const childRunId = entry.data?.childRunId;
|
|
948
|
+
return typeof childRunId === 'string' && childRunId !== '' ? childRunId : null;
|
|
949
|
+
}
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** 자식 원장의 마지막 pr-open-failed/child-error data.error 꼬리 — 없으면 null. */
|
|
954
|
+
function outcomeChildLedgerReason(childEntries: readonly RunLedgerEntry[]): { reason: string; reasonFrom: string } | null {
|
|
955
|
+
for (let i = childEntries.length - 1; i >= 0; i -= 1) {
|
|
956
|
+
const entry = childEntries[i]!;
|
|
957
|
+
if (!OUTCOME_REASON_LEDGER_EVENTS.has(entry.event)) continue;
|
|
958
|
+
const error = entry.data?.error;
|
|
959
|
+
if (typeof error !== 'string' || error === '') continue;
|
|
960
|
+
return { reason: error, reasonFrom: `child-ledger:${entry.event}` };
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* TA 카드·PR 종결 사실을 기간으로 센다(읽기 전용). 창은 «종료 시각»에 걸린다.
|
|
967
|
+
* 종결 사유 원문은 부모 runId → pod-child-run 매핑 → 자식 원장의 마지막
|
|
968
|
+
* pr-open-failed/child-error data.error 꼬리에서 읽는다.
|
|
969
|
+
*/
|
|
970
|
+
export function readTaskOutcomes(
|
|
971
|
+
window: { sinceMs?: number; untilMs?: number; terminalKind?: 'card' | 'pr' } = {},
|
|
972
|
+
deps: Pick<TasksCliDeps, 'taskStatePath' | 'outcomesLimit'> = {},
|
|
973
|
+
snapshot?: Record<string, TaskCard>,
|
|
974
|
+
): Omit<TaskOutcomeReport, 'since' | 'until'> {
|
|
975
|
+
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
976
|
+
const cards = snapshot ?? readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
|
|
977
|
+
const limit = deps.outcomesLimit ?? OUTCOMES_QUERY_LIMIT;
|
|
978
|
+
const candidates: Array<{ card: TaskCard; terminal: TaskCard['prTerminal']; endedAt: string;
|
|
979
|
+
cardTerminalAt: string | null; prTerminalAt: string | null; terminalKind: 'card' | 'pr'; countReason: string }> = [];
|
|
980
|
+
let undatedCards = 0;
|
|
981
|
+
let openCards = 0;
|
|
982
|
+
for (const card of Object.values(cards)) {
|
|
983
|
+
if (!card || typeof card.id !== 'string') continue;
|
|
984
|
+
const terminal = boundPrTerminal(card);
|
|
985
|
+
// 결과란 «끝난 카드»다 — 종결 상태이거나 PR 이 종결로 관측된 카드. 아직 도는 카드는 창 대상이 아니다(따로 센다).
|
|
986
|
+
if (!OUTCOME_TERMINAL_STATUSES.has(card.status) && terminal === undefined) { openCards++; continue; }
|
|
987
|
+
const terminalHistory = outcomeTerminalHistoryLine(card);
|
|
988
|
+
const successfulStatus = card.status === 'done' || card.status === 'landed' || card.status === 'landed-by-hand';
|
|
989
|
+
const manualClosure = (card.status === 'done' || card.status === 'landed-by-hand')
|
|
990
|
+
&& terminalHistory?.event === CLOSED_BY_HAND_EVENT;
|
|
991
|
+
const failedStatus = card.status === 'failed' || card.status === 'launch-failed';
|
|
992
|
+
const history = successfulStatus
|
|
993
|
+
? (OUTCOME_SUCCESS_HISTORY_EVENTS.has(terminalHistory?.event ?? '') || manualClosure ? terminalHistory : undefined)
|
|
994
|
+
: failedStatus && (terminalHistory?.event === 'failed' || terminalHistory?.event === 'launch-failed')
|
|
995
|
+
? terminalHistory : undefined;
|
|
996
|
+
const validAt = (at: string | undefined): string | null => at && Number.isFinite(Date.parse(at)) ? at : null;
|
|
997
|
+
const cardTerminalAt = validAt(history?.at);
|
|
998
|
+
const prTerminalAt = validAt(terminal?.at);
|
|
999
|
+
if (cardTerminalAt === null && prTerminalAt === null) { undatedCards++; continue; }
|
|
1000
|
+
const inWindow = (at: string | null): at is string => at !== null
|
|
1001
|
+
&& (window.sinceMs === undefined || Date.parse(at) >= window.sinceMs)
|
|
1002
|
+
&& (window.untilMs === undefined || Date.parse(at) <= window.untilMs);
|
|
1003
|
+
if (inWindow(cardTerminalAt)) {
|
|
1004
|
+
candidates.push({ card, terminal, endedAt: cardTerminalAt, cardTerminalAt, prTerminalAt,
|
|
1005
|
+
terminalKind: 'card', countReason: `card:${card.status}:${history!.event}@${cardTerminalAt}` });
|
|
1006
|
+
}
|
|
1007
|
+
if (inWindow(prTerminalAt)) {
|
|
1008
|
+
candidates.push({ card, terminal, endedAt: prTerminalAt, cardTerminalAt, prTerminalAt,
|
|
1009
|
+
terminalKind: 'pr', countReason: `pr:${terminal!.state}:#${terminal!.pr}@${prTerminalAt}` });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const overlap = new Map<string, Set<'card' | 'pr'>>();
|
|
1013
|
+
for (const item of candidates) {
|
|
1014
|
+
const kinds = overlap.get(item.card.id) ?? new Set<'card' | 'pr'>();
|
|
1015
|
+
kinds.add(item.terminalKind);
|
|
1016
|
+
overlap.set(item.card.id, kinds);
|
|
1017
|
+
}
|
|
1018
|
+
const overlapCardIds = [...overlap].filter(([, kinds]) => kinds.size === 2).map(([id]) => id).sort();
|
|
1019
|
+
const visible = window.terminalKind ? candidates.filter((item) => item.terminalKind === window.terminalKind) : candidates;
|
|
1020
|
+
visible.sort((a, b) => Date.parse(b.endedAt) - Date.parse(a.endedAt) || a.card.id.localeCompare(b.card.id)
|
|
1021
|
+
|| a.terminalKind.localeCompare(b.terminalKind));
|
|
1022
|
+
const limitReached = visible.length > limit;
|
|
1023
|
+
const selected = limitReached ? visible.slice(0, limit) : visible;
|
|
1024
|
+
const stateRoot = dirname(statePath);
|
|
1025
|
+
const localLedgerDirs = implementLedgerDirectories(statePath, false);
|
|
1026
|
+
const runExits = readRunExits(stateRoot);
|
|
1027
|
+
let childLedgerObserved = false;
|
|
1028
|
+
let reasonObserved = false;
|
|
1029
|
+
const rows = selected.map(({ card, terminal, endedAt, cardTerminalAt, prTerminalAt, terminalKind, countReason }): TaskOutcomeRow => {
|
|
1030
|
+
const number = cardPrNumber(card);
|
|
1031
|
+
// 종결 사유 원문 — 부모 runId → pod-child-run 매핑 → 자식 원장 꼬리. 원장을 못 찾으면 null.
|
|
1032
|
+
const remoteDir = card.parentHost ? remoteLedgerMirrorDir(stateRoot, card.parentHost.host) : null;
|
|
1033
|
+
// 원격 부모는 해당 호스트 거울에서만 결속한다. 로컬 동명 런을 원격 증거로 오인하지 않는다.
|
|
1034
|
+
const ledgerDirs = remoteDir ? [remoteDir] : localLedgerDirs;
|
|
1035
|
+
const ledgerDir = card.runId ? ledgerDirs.find((dir) => loadRunLedgerSync(card.runId!, dir) !== null) : undefined;
|
|
1036
|
+
const mappedChildRunId = ledgerDir ? outcomeChildRunId(card, ledgerDir) : null;
|
|
1037
|
+
const childBindingMismatch = card.runChildId && mappedChildRunId && card.runChildId !== mappedChildRunId;
|
|
1038
|
+
const childRunId = childBindingMismatch ? null : mappedChildRunId;
|
|
1039
|
+
const childEntries = childRunId === null || !ledgerDir ? null : loadRunLedgerSync(childRunId, ledgerDir);
|
|
1040
|
+
if (childEntries !== null) childLedgerObserved = true;
|
|
1041
|
+
const reasonTail = childEntries === null ? null : outcomeChildLedgerReason(childEntries);
|
|
1042
|
+
const cardReason = terminalKind === 'card' && (card.status === 'failed' || card.status === 'launch-failed') ? reasonTail : null;
|
|
1043
|
+
if (cardReason !== null) reasonObserved = true;
|
|
1044
|
+
const exit = card.runId ? runExits.find((row) => row.runId === card.runId) : undefined;
|
|
1045
|
+
const lastLines = exit?.lastLines?.length ? exit.lastLines : null;
|
|
1046
|
+
return {
|
|
1047
|
+
cell: card.checklistId ?? null,
|
|
1048
|
+
card: card.id,
|
|
1049
|
+
runId: card.runId ?? null,
|
|
1050
|
+
childRunId,
|
|
1051
|
+
host: card.parentHost?.host ?? null,
|
|
1052
|
+
endedAt,
|
|
1053
|
+
cardTerminalAt,
|
|
1054
|
+
prTerminalAt,
|
|
1055
|
+
ghTerminalAt: null,
|
|
1056
|
+
ghTerminalMissing: 'not-stored-in-card',
|
|
1057
|
+
terminalKind,
|
|
1058
|
+
countReason,
|
|
1059
|
+
cardStatus: card.status,
|
|
1060
|
+
pr: number === undefined ? null : { number, state: terminal?.state ?? null },
|
|
1061
|
+
reason: cardReason?.reason ?? null,
|
|
1062
|
+
reasonFrom: cardReason?.reasonFrom ?? null,
|
|
1063
|
+
evidence: {
|
|
1064
|
+
childLedger: reasonTail ? { event: reasonTail.reasonFrom.slice('child-ledger:'.length), error: reasonTail.reason } : null,
|
|
1065
|
+
childLedgerMissing: reasonTail ? null : !card.runId ? 'run-id-missing'
|
|
1066
|
+
: remoteDir && !existsSync(remoteDir) ? 'remote-ledger-mirror-missing'
|
|
1067
|
+
: !ledgerDir ? (remoteDir ? 'remote-parent-ledger-missing' : 'parent-ledger-missing')
|
|
1068
|
+
: childBindingMismatch ? 'parent-child-binding-mismatch'
|
|
1069
|
+
: childRunId === null ? 'parent-child-binding-missing'
|
|
1070
|
+
: childEntries === null ? (remoteDir ? 'remote-child-ledger-missing' : 'child-ledger-missing') : 'child-ledger-error-tail-missing',
|
|
1071
|
+
lastLines,
|
|
1072
|
+
lastLinesMissing: lastLines ? null : !card.runId ? 'run-id-missing'
|
|
1073
|
+
: !exit ? 'run-exit-missing' : 'run-exit-last-lines-missing',
|
|
1074
|
+
},
|
|
1075
|
+
};
|
|
1076
|
+
});
|
|
1077
|
+
const reasonTally = new Map<string, number>();
|
|
1078
|
+
for (const row of rows) {
|
|
1079
|
+
if (row.reason === null) continue;
|
|
1080
|
+
reasonTally.set(row.reason, (reasonTally.get(row.reason) ?? 0) + 1);
|
|
1081
|
+
}
|
|
1082
|
+
const reasons = [...reasonTally.entries()]
|
|
1083
|
+
.map(([reason, count]) => ({ reason, count }))
|
|
1084
|
+
.sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason));
|
|
1085
|
+
const failedCardRows = rows.filter((row) => row.terminalKind === 'card'
|
|
1086
|
+
&& (row.cardStatus === 'failed' || row.cardStatus === 'launch-failed'));
|
|
1087
|
+
const missingReason = failedCardRows.filter((row) => row.reason === null).length;
|
|
1088
|
+
const warnings = [
|
|
1089
|
+
limitReached ? `사건 조회 상한 ${limit} 도달 — 창 안 ${visible.length}행 중 ${rows.length}행만 셌다(집계도 이 행들만 센다)` : null,
|
|
1090
|
+
missingReason > 0 ? `실패 카드 종결 사유 원문 없음 — ${missingReason}/${failedCardRows.length}행(증거 부재 이유는 행별 evidence 참조)` : null,
|
|
1091
|
+
undatedCards > 0 ? `종결 시각 미보존 ${undatedCards}장 — 기간 귀속 불가(창 밖 카드 포함)` : null,
|
|
1092
|
+
].filter((line): line is string => line !== null);
|
|
1093
|
+
return {
|
|
1094
|
+
rows,
|
|
1095
|
+
aggregate: { cards: new Set(rows.map((row) => row.card)).size,
|
|
1096
|
+
cardTerminals: rows.filter((row) => row.terminalKind === 'card').length,
|
|
1097
|
+
prTerminals: rows.filter((row) => row.terminalKind === 'pr').length,
|
|
1098
|
+
failedCardTerminals: failedCardRows.length, reasons, missingReason },
|
|
1099
|
+
limitReached,
|
|
1100
|
+
stopReason: limitReached ? 'page-limit' : null,
|
|
1101
|
+
childLedgerObserved,
|
|
1102
|
+
reasonObserved,
|
|
1103
|
+
undatedCards,
|
|
1104
|
+
undatedPeriodAttribution: 'unknown',
|
|
1105
|
+
overlapCardIds,
|
|
1106
|
+
openCards,
|
|
1107
|
+
...(warnings.length ? { warning: warnings.join(' · ') } : {}),
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
type DoctorTruth = true | false | 'unknown';
|
|
1112
|
+
type DoctorCondition = { value: DoctorTruth; source: string; detail: string };
|
|
1113
|
+
type DoctorPr = { number: number; state: string | null; draft: boolean | null; head: string | null; changedFiles: number | null };
|
|
1114
|
+
|
|
1115
|
+
export interface TaskDoctorReport {
|
|
1116
|
+
card: string;
|
|
1117
|
+
cell: string | null;
|
|
1118
|
+
version: string;
|
|
1119
|
+
runId: string | null;
|
|
1120
|
+
childRunId: string | null;
|
|
1121
|
+
host: string | null;
|
|
1122
|
+
head: string | null;
|
|
1123
|
+
terminalReason: string | null;
|
|
1124
|
+
terminalReasonSource: string | null;
|
|
1125
|
+
lastGate: { passed: boolean | null; status: string | null; head: string | null; at: string | null; source: string } | null;
|
|
1126
|
+
pr: DoctorPr | null;
|
|
1127
|
+
conditions: { currentYellowUnheldNoOtherPr: DoctorCondition; noPreservedImplementation: DoctorCondition;
|
|
1128
|
+
environmentFailure: DoctorCondition; failureRoundsBelowTwo: DoctorCondition };
|
|
1129
|
+
unknownSources: string[];
|
|
1130
|
+
nextAction: 'relaunch' | 'harvest' | 'reauthor' | 'human';
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function doctorCondition(value: DoctorTruth, source: string, detail: string): DoctorCondition { return { value, source, detail }; }
|
|
1134
|
+
function doctorObject(value: unknown): Record<string, unknown> | null {
|
|
1135
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
1136
|
+
}
|
|
1137
|
+
function doctorString(value: unknown): string | null { return typeof value === 'string' && value.trim() ? value : null; }
|
|
1138
|
+
function doctorTerminal(entry: import('../self-implement/run-ledger.js').RunLedgerEntry): boolean {
|
|
1139
|
+
if (entry.event === 'stop') return true;
|
|
1140
|
+
if (entry.event === 'run-status') return ['failed', 'completed', 'cancelled', 'parked'].includes(String(entry.data.runStatus));
|
|
1141
|
+
return entry.event === 'terminal' && (typeof entry.data.ok === 'boolean' || typeof entry.data.terminal === 'string');
|
|
1142
|
+
}
|
|
1143
|
+
function doctorTerminalFailed(entry: import('../self-implement/run-ledger.js').RunLedgerEntry): boolean {
|
|
1144
|
+
if (entry.event === 'stop') return true;
|
|
1145
|
+
if (entry.event === 'run-status') return entry.data.runStatus === 'failed';
|
|
1146
|
+
return entry.data.ok === false || ['failed', 'aborted', 'gate-failed', 'review-blocked', 'timed-out'].includes(String(entry.data.terminal));
|
|
1147
|
+
}
|
|
1148
|
+
function defaultDoctorLedger(runId: string, statePath: string): import('../self-implement/run-ledger.js').RunLedgerEntry[] | null {
|
|
1149
|
+
let found: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1150
|
+
for (const dir of implementLedgerDirectories(statePath, false)) {
|
|
1151
|
+
const loaded = loadRunLedgerWithMetadata(runId, dir);
|
|
1152
|
+
if (!loaded) continue;
|
|
1153
|
+
if (loaded.skippedTrailingBytes) throw new Error(`incomplete run ledger: ${runId}`);
|
|
1154
|
+
if (found && JSON.stringify(found) !== JSON.stringify(loaded.entries)) throw new Error(`conflicting run ledgers: ${runId}`);
|
|
1155
|
+
found = loaded.entries;
|
|
1156
|
+
}
|
|
1157
|
+
return found;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/** Read-only relaunch preflight. Each decision names its evidence, and a missing adapter remains unknown. */
|
|
1161
|
+
export function readTaskDoctor(target: string, opts: { traffic?: string } = {}, deps: TasksCliDeps = {}): TaskDoctorReport {
|
|
1162
|
+
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
1163
|
+
const state = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath);
|
|
1164
|
+
const cards = Object.values(state.tasks ?? {});
|
|
1165
|
+
const matches = cards.filter((item) => item?.id === target || item?.runId === target || item?.runChildId === target);
|
|
1166
|
+
if (matches.length !== 1) throw new Error(matches.length ? `카드 결속 모호: ${target}` : `카드·런 없음: ${target}`);
|
|
1167
|
+
const card = matches[0]!;
|
|
1168
|
+
const version = (deps.doctorVersion ?? checklistDevVersion)();
|
|
1169
|
+
const cell = card.checklistId ?? null;
|
|
1170
|
+
const run = deps.doctorRun ?? ((command: string, args: string[]) => spawnSync(command, args,
|
|
1171
|
+
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000, maxBuffer: 4 * 1024 * 1024 }));
|
|
1172
|
+
|
|
1173
|
+
let yellow: DoctorCondition = doctorCondition('unknown', 'checklist', '카드의 칸을 모른다');
|
|
1174
|
+
if (cell) {
|
|
1175
|
+
try {
|
|
1176
|
+
const snapshot = (deps.doctorChecklist ?? listChecklist)(version);
|
|
1177
|
+
const item = snapshot.items.find((entry) => entry.id === cell);
|
|
1178
|
+
const versionMatches = !card.checklistVersion || card.checklistVersion === version;
|
|
1179
|
+
yellow = doctorCondition(item ? item.status === 'yellow' && versionMatches : false, `checklist:${version}:${cell}`,
|
|
1180
|
+
item ? `status=${item.status}${versionMatches ? '' : `, cardVersion=${card.checklistVersion}`}` : '현재 판에 칸 없음');
|
|
1181
|
+
} catch { yellow = doctorCondition('unknown', `checklist:${version}:${cell}`, '체크리스트 조회 실패'); }
|
|
1182
|
+
}
|
|
1183
|
+
let unheld: DoctorCondition = doctorCondition('unknown', 'traffic', 'traffic 경로 없음 (--traffic 필요)');
|
|
1184
|
+
if (opts.traffic) {
|
|
1185
|
+
try {
|
|
1186
|
+
const traffic = doctorObject(JSON.parse(readFileSync(opts.traffic, 'utf8')));
|
|
1187
|
+
const holds = traffic?.hold;
|
|
1188
|
+
unheld = Array.isArray(holds) && holds.every((id) => typeof id === 'string') && cell
|
|
1189
|
+
? doctorCondition(!holds.includes(cell), `traffic:${opts.traffic}`, holds.includes(cell) ? 'hold 활성' : 'hold 없음')
|
|
1190
|
+
: doctorCondition('unknown', `traffic:${opts.traffic}`, 'hold 배열 또는 칸 결속 불명');
|
|
1191
|
+
} catch { unheld = doctorCondition('unknown', `traffic:${opts.traffic}`, 'traffic 읽기 실패'); }
|
|
1192
|
+
}
|
|
1193
|
+
let noOtherPr: DoctorCondition = doctorCondition('unknown', 'gh:open-pr-list', '열린 PR 조회 실패');
|
|
1194
|
+
try {
|
|
1195
|
+
const result = run('gh', ['pr', 'list', '--state', 'open', '--limit', '500', '--json', 'number,body']);
|
|
1196
|
+
if (result.status === 0) {
|
|
1197
|
+
const rows: unknown = JSON.parse(result.stdout);
|
|
1198
|
+
if (Array.isArray(rows) && rows.length < 500 && rows.every((row) =>
|
|
1199
|
+
Number.isSafeInteger(doctorObject(row)?.number) && typeof doctorObject(row)?.body === 'string')) {
|
|
1200
|
+
const conflicts = rows.filter((row) => {
|
|
1201
|
+
const pr = doctorObject(row)!;
|
|
1202
|
+
return cellIdFromBody(pr.body as string) === cell && pr.number !== cardPrNumber(card);
|
|
1203
|
+
}).map((row) => doctorObject(row)!.number as number);
|
|
1204
|
+
noOtherPr = doctorCondition(conflicts.length === 0, 'gh:open-pr-list:cell-marker',
|
|
1205
|
+
conflicts.length ? `다른 열린 PR: ${conflicts.map((id) => `#${id}`).join(',')}` : '같은 칸의 다른 열린 PR 없음');
|
|
1206
|
+
} else noOtherPr = doctorCondition('unknown', 'gh:open-pr-list', 'PR 목록 형식 불명 또는 500건 상한');
|
|
1207
|
+
}
|
|
1208
|
+
} catch { /* unknown stays explicit */ }
|
|
1209
|
+
const firstParts = [yellow, unheld, noOtherPr];
|
|
1210
|
+
const firstValue: DoctorTruth = firstParts.some((part) => part.value === 'unknown') ? 'unknown'
|
|
1211
|
+
: firstParts.every((part) => part.value === true);
|
|
1212
|
+
const currentYellowUnheldNoOtherPr = doctorCondition(firstValue,
|
|
1213
|
+
firstParts.map((part) => part.source).join(' + '), firstParts.map((part) => part.detail).join('; '));
|
|
1214
|
+
|
|
1215
|
+
const prNumber = cardPrNumber(card);
|
|
1216
|
+
let pr: DoctorPr | null = prNumber === undefined ? null
|
|
1217
|
+
: { number: prNumber, state: null, draft: null, head: null, changedFiles: null };
|
|
1218
|
+
let prFiles: Array<{ path: string; additions: number; deletions: number }> | null = null;
|
|
1219
|
+
let prSource = 'gh:bound-pr';
|
|
1220
|
+
if (prNumber !== undefined) {
|
|
1221
|
+
try {
|
|
1222
|
+
const result = run('gh', ['pr', 'view', String(prNumber), '--json', 'number,state,isDraft,headRefOid,changedFiles,files']);
|
|
1223
|
+
if (result.status === 0) {
|
|
1224
|
+
const view = doctorObject(JSON.parse(result.stdout));
|
|
1225
|
+
const files = view?.files;
|
|
1226
|
+
const changedFiles = view?.changedFiles;
|
|
1227
|
+
if (view?.number === prNumber && typeof view.state === 'string' && typeof view.isDraft === 'boolean'
|
|
1228
|
+
&& Number.isSafeInteger(changedFiles) && (changedFiles as number) >= 0 && Array.isArray(files)
|
|
1229
|
+
&& files.every((file) => typeof doctorObject(file)?.path === 'string'
|
|
1230
|
+
&& Number.isSafeInteger(doctorObject(file)?.additions) && Number.isSafeInteger(doctorObject(file)?.deletions))) {
|
|
1231
|
+
pr = { number: prNumber, state: view.state, draft: view.isDraft,
|
|
1232
|
+
head: doctorString(view.headRefOid), changedFiles: changedFiles as number };
|
|
1233
|
+
prFiles = files.map((file) => doctorObject(file) as { path: string; additions: number; deletions: number });
|
|
1234
|
+
} else prSource = 'gh:bound-pr:malformed';
|
|
1235
|
+
} else prSource = 'gh:bound-pr:unreadable';
|
|
1236
|
+
} catch { prSource = 'gh:bound-pr:unreadable'; }
|
|
1237
|
+
} else prSource = 'card:pr-unbound';
|
|
1238
|
+
let noPreservedImplementation = doctorCondition('unknown', prSource, 'PR diff를 못 읽었다');
|
|
1239
|
+
if (pr && prFiles && pr.changedFiles === prFiles.length) {
|
|
1240
|
+
const docsOnly = prFiles.length > 0 && prFiles.every((file) => /(?:^|\/)[^/]+\.md$/i.test(file.path));
|
|
1241
|
+
const empty = pr.changedFiles === 0;
|
|
1242
|
+
noPreservedImplementation = doctorCondition(empty || docsOnly, `gh:pr-${pr.number}:files`,
|
|
1243
|
+
empty ? 'diff 0' : docsOnly ? 'docs-only diff' : '보존할 구현 diff 있음');
|
|
1244
|
+
} else if (pr && prFiles) noPreservedImplementation = doctorCondition('unknown', `gh:pr-${pr.number}:files`, 'changedFiles와 파일 목록 수 불일치');
|
|
1245
|
+
|
|
1246
|
+
const ledgerId = card.runChildId;
|
|
1247
|
+
let entries: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1248
|
+
let ledgerSource = ledgerId ? `run-ledger:${ledgerId}` : 'card:child-run-unbound';
|
|
1249
|
+
if (ledgerId) {
|
|
1250
|
+
try { entries = (deps.doctorLedger ?? defaultDoctorLedger)(ledgerId, statePath); }
|
|
1251
|
+
catch { ledgerSource += ':unreadable'; }
|
|
1252
|
+
}
|
|
1253
|
+
let parentEntries: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1254
|
+
let parentLedgerSource = card.runId ? `run-ledger:${card.runId}` : 'card:parent-run-unbound';
|
|
1255
|
+
if (card.runId) {
|
|
1256
|
+
try { parentEntries = (deps.doctorLedger ?? defaultDoctorLedger)(card.runId, statePath); }
|
|
1257
|
+
catch { parentLedgerSource += ':unreadable'; }
|
|
1258
|
+
}
|
|
1259
|
+
const terminal = entries?.slice().reverse().find(doctorTerminal);
|
|
1260
|
+
const terminalData = doctorObject(terminal?.data);
|
|
1261
|
+
const parentTerminal = parentEntries?.slice().reverse().find(doctorTerminal);
|
|
1262
|
+
const parentData = doctorObject(parentTerminal?.data);
|
|
1263
|
+
const parentChildEvidence = doctorObject(parentData?.childEvidence);
|
|
1264
|
+
const prFailure = doctorObject(terminalData?.prFailure);
|
|
1265
|
+
const childTerminalReason = doctorString(terminalData?.terminationReason) ?? doctorString(terminalData?.cause)
|
|
1266
|
+
?? doctorString(terminalData?.reason) ?? doctorString(terminalData?.error)
|
|
1267
|
+
?? doctorString(prFailure?.errorTail);
|
|
1268
|
+
const childReasonSource = childTerminalReason ? `${ledgerSource}:${terminal?.event}` : null;
|
|
1269
|
+
const parentTerminalReason = doctorString(parentData?.terminationReason) ?? doctorString(parentData?.cause)
|
|
1270
|
+
?? doctorString(parentData?.reason) ?? doctorString(parentChildEvidence?.terminationReason)
|
|
1271
|
+
?? doctorString(parentChildEvidence?.error) ?? null;
|
|
1272
|
+
const cardTerminal = card.history?.slice().reverse().find((item) =>
|
|
1273
|
+
(item.event === 'failed' || item.event === 'launch-failed') && (!item.runId || item.runId === card.runId));
|
|
1274
|
+
const terminalReason = childTerminalReason ?? parentTerminalReason ?? doctorString(cardTerminal?.detail);
|
|
1275
|
+
const reauthorPattern = /scope[- ]escape|UNCONVERGEABLE|goal[- ]revision[- ]required/i;
|
|
1276
|
+
const environmentPattern = /worktree add.{0,100}(?:timed? ?out|timeout)|(?:timed? ?out|timeout).{0,100}worktree add|model catalog.{0,100}unknown|unknown.{0,100}model catalog|stale pod image|pod image.{0,100}stale/i;
|
|
1277
|
+
const childEvidence = doctorObject(terminalData?.childEvidence);
|
|
1278
|
+
const structuredReauthorSource = terminalData?.scopeEscape === true ? `${ledgerSource}:${terminal?.event}:scopeEscape`
|
|
1279
|
+
: childEvidence?.scopeEscape === true ? `${ledgerSource}:${terminal?.event}:childEvidence.scopeEscape`
|
|
1280
|
+
: reauthorPattern.test(doctorString(terminalData?.supervisorVerdict) ?? '')
|
|
1281
|
+
? `${ledgerSource}:${terminal?.event}:supervisorVerdict`
|
|
1282
|
+
: parentData?.scopeEscape === true ? `${parentLedgerSource}:${parentTerminal?.event}:scopeEscape`
|
|
1283
|
+
: parentChildEvidence?.scopeEscape === true ? `${parentLedgerSource}:${parentTerminal?.event}:childEvidence.scopeEscape`
|
|
1284
|
+
: reauthorPattern.test(doctorString(parentChildEvidence?.supervisorVerdict) ?? '')
|
|
1285
|
+
? `${parentLedgerSource}:${parentTerminal?.event}:childEvidence.supervisorVerdict` : null;
|
|
1286
|
+
const structuredReauthor = structuredReauthorSource !== null;
|
|
1287
|
+
const scopeSignals = [terminalData?.scopeEscape, childEvidence?.scopeEscape,
|
|
1288
|
+
parentData?.scopeEscape, parentChildEvidence?.scopeEscape].filter((value): value is boolean => typeof value === 'boolean');
|
|
1289
|
+
const scopeSignalConflict = scopeSignals.includes(true) && scopeSignals.includes(false);
|
|
1290
|
+
let environmentFailure = doctorCondition('unknown', ledgerSource, '자식 종결 원장 또는 원문 없음');
|
|
1291
|
+
if (!terminal) environmentFailure = doctorCondition('unknown', ledgerSource, '자식 종결 원장 결손');
|
|
1292
|
+
else if (!parentTerminal) environmentFailure = doctorCondition('unknown', parentLedgerSource,
|
|
1293
|
+
'부모 종결 원장 결손·충돌 — scopeEscape 재확인 불가');
|
|
1294
|
+
else if (!doctorTerminalFailed(parentTerminal)) environmentFailure = doctorCondition('unknown',
|
|
1295
|
+
`${parentLedgerSource}:${parentTerminal.event}`, '부모 런이 실패로 종결되지 않음');
|
|
1296
|
+
else if (childTerminalReason && parentTerminalReason
|
|
1297
|
+
&& !parentTerminalReason.includes(childTerminalReason) && !childTerminalReason.includes(parentTerminalReason)) {
|
|
1298
|
+
environmentFailure = doctorCondition('unknown', `${ledgerSource} + ${parentLedgerSource}`,
|
|
1299
|
+
'부모·자식 종결 사유 불일치');
|
|
1300
|
+
} else if (scopeSignalConflict) environmentFailure = doctorCondition('unknown', `${ledgerSource} + ${parentLedgerSource}`,
|
|
1301
|
+
'부모·자식 scopeEscape 신호 불일치');
|
|
1302
|
+
else if (structuredReauthor) environmentFailure = doctorCondition(false, structuredReauthorSource!, '구조화된 재저작 신호');
|
|
1303
|
+
else if (childTerminalReason) environmentFailure = doctorCondition(
|
|
1304
|
+
!doctorTerminalFailed(terminal) || reauthorPattern.test(childTerminalReason) ? false : environmentPattern.test(childTerminalReason),
|
|
1305
|
+
childReasonSource ?? ledgerSource,
|
|
1306
|
+
!doctorTerminalFailed(terminal) ? '자식 런이 실패로 종결되지 않음' : reauthorPattern.test(childTerminalReason)
|
|
1307
|
+
? '골 재저작 필요' : environmentPattern.test(childTerminalReason) ? '허용된 환경 실패' : '허용된 환경 실패 아님');
|
|
1308
|
+
if (card.status !== 'failed' && card.status !== 'launch-failed') environmentFailure = doctorCondition(false,
|
|
1309
|
+
`card:${card.id}:status`, `현재 카드 status=${card.status} — 재발사 대상 실패 카드 아님`);
|
|
1310
|
+
if (pr?.state === 'MERGED') environmentFailure = doctorCondition(false,
|
|
1311
|
+
`gh:pr-${pr.number}:state`, '결속 PR이 이미 병합됨');
|
|
1312
|
+
|
|
1313
|
+
const roundCards = cell ? cards.filter((item) => item?.checklistId === cell) : [];
|
|
1314
|
+
const knownFailureRuns = new Set<string>();
|
|
1315
|
+
let unboundFailureCards = 0;
|
|
1316
|
+
let ambiguousRounds = false;
|
|
1317
|
+
for (const item of roundCards) {
|
|
1318
|
+
const failures = (item.history ?? []).filter((entry) => entry.event === 'failed' || entry.event === 'launch-failed');
|
|
1319
|
+
const itemRuns = new Set(failures.map((entry) => doctorString(entry.runId)).filter((id): id is string => id !== null));
|
|
1320
|
+
const statusFailed = item.status === 'failed' || item.status === 'launch-failed';
|
|
1321
|
+
if (statusFailed && item.runId) itemRuns.add(item.runId);
|
|
1322
|
+
for (const id of itemRuns) knownFailureRuns.add(id);
|
|
1323
|
+
const unbound = failures.some((entry) => !doctorString(entry.runId));
|
|
1324
|
+
if (unbound && itemRuns.size) ambiguousRounds = true;
|
|
1325
|
+
else if (!itemRuns.size && (unbound || statusFailed)) unboundFailureCards++;
|
|
1326
|
+
}
|
|
1327
|
+
const failureRounds = knownFailureRuns.size + unboundFailureCards;
|
|
1328
|
+
const failureRoundsBelowTwo = doctorCondition(cell && !ambiguousRounds ? failureRounds < 2 : 'unknown',
|
|
1329
|
+
'card-status+history:same-cell-failures', !cell ? '같은 칸 실패 이력 불명'
|
|
1330
|
+
: ambiguousRounds ? 'runId 없는 실패 사건과 결속된 런이 섞여 라운드 중복 불명' : `실패 라운드 ${failureRounds}`);
|
|
1331
|
+
const lastRegate = card.regates?.at(-1);
|
|
1332
|
+
const childGate = entries?.slice().reverse().find((entry) => entry.event === 'gated');
|
|
1333
|
+
const useRegate = lastRegate && (!childGate || Date.parse(lastRegate.at) >= Date.parse(childGate.timestamp ?? ''));
|
|
1334
|
+
const lastGate = useRegate && lastRegate ? { passed: lastRegate.passed ?? null, status: lastRegate.status ?? null,
|
|
1335
|
+
head: lastRegate.head, at: lastRegate.at, source: 'card:regates' }
|
|
1336
|
+
: childGate ? { passed: typeof childGate.data.passed === 'boolean' ? childGate.data.passed : null,
|
|
1337
|
+
status: childGate.data.gateExecuted === false ? 'skipped' : 'executed',
|
|
1338
|
+
head: doctorString(childGate.data.head),
|
|
1339
|
+
at: childGate.timestamp ?? null, source: `${ledgerSource}:gated` } : null;
|
|
1340
|
+
const conditions = { currentYellowUnheldNoOtherPr, noPreservedImplementation, environmentFailure, failureRoundsBelowTwo };
|
|
1341
|
+
const unknownSources = Object.entries(conditions).filter(([, part]) => part.value === 'unknown')
|
|
1342
|
+
.map(([name, part]) => `${name}:${part.source}`);
|
|
1343
|
+
const nextAction = unknownSources.length ? 'human'
|
|
1344
|
+
: structuredReauthor || (terminalReason && reauthorPattern.test(terminalReason)) || failureRoundsBelowTwo.value === false
|
|
1345
|
+
? 'reauthor' : noPreservedImplementation.value === false ? 'harvest'
|
|
1346
|
+
: Object.values(conditions).every((part) => part.value === true) ? 'relaunch' : 'human';
|
|
1347
|
+
return { card: card.id, cell, version, runId: card.runId ?? null, childRunId: card.runChildId ?? null,
|
|
1348
|
+
host: card.parentHost?.host ?? null, head: pr?.head ?? lastRegate?.head ?? card.adoptedPr?.head ?? card.prTerminal?.head ?? null,
|
|
1349
|
+
terminalReason, terminalReasonSource: childReasonSource
|
|
1350
|
+
?? (parentTerminalReason && parentTerminal ? `run-ledger:${card.runId}:${parentTerminal.event}`
|
|
1351
|
+
: cardTerminal ? `card-history:${cardTerminal.event}` : null),
|
|
1352
|
+
lastGate, pr, conditions, unknownSources, nextAction };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/** MODEL-SIZE-OUTCOME-MEASURE — `tasks outcomes --by` 가 아는 집계 축. */
|
|
1356
|
+
export const TASK_OUTCOME_BY_DIMENSIONS = ['model', 'size'] as const;
|
|
1357
|
+
|
|
1358
|
+
/** 못 읽은 값의 이름 — 0 이나 빈 문자열로 지어내지 않는다. */
|
|
1359
|
+
export const TASK_OUTCOME_UNKNOWN = 'unknown';
|
|
1360
|
+
|
|
1361
|
+
/** 카드 하나의 결과 — PR 착지 · 범위 이탈 · 수렴 실패 · 결과 줄 없음. */
|
|
1362
|
+
export type TaskOutcomeModelSizeResult = 'landed' | 'scope-escape' | 'convergence-failure' | 'no-result-line';
|
|
1363
|
+
|
|
1364
|
+
/** 카드 하나의 측정 줄 — 자식 모델 · 등급 · 조각 크기(대상 경로 수 · 바꿀 동작 수) · 결과. */
|
|
1365
|
+
export interface TaskOutcomeModelSizeCard {
|
|
1366
|
+
card: string;
|
|
1367
|
+
/** `provider/model`(카드 childLlm) · 못 읽으면 `unknown`. */
|
|
1368
|
+
model: string;
|
|
1369
|
+
/** 디스패처 난이도(하|중|상|특) · 못 읽으면 `unknown`. */
|
|
1370
|
+
tier: string;
|
|
1371
|
+
size: { targetPaths: number | typeof TASK_OUTCOME_UNKNOWN; behaviors: number | typeof TASK_OUTCOME_UNKNOWN };
|
|
1372
|
+
result: TaskOutcomeModelSizeResult;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/** 모델×크기 칸 하나 — 성공률 · 표본 수 · unknown 수. */
|
|
1376
|
+
export interface TaskOutcomeModelSizeCell {
|
|
1377
|
+
model: string;
|
|
1378
|
+
/** 크기 칸 이름(`paths=<n>·behaviors=<n>`) · 못 읽은 칸은 `unknown`. */
|
|
1379
|
+
size: string;
|
|
1380
|
+
samples: number;
|
|
1381
|
+
landed: number;
|
|
1382
|
+
/** landed / samples. */
|
|
1383
|
+
successRate: number | null;
|
|
1384
|
+
/** 이 칸에서 한 축이라도 못 읽은 카드 수. */
|
|
1385
|
+
unknown: number;
|
|
1386
|
+
results: Record<TaskOutcomeModelSizeResult, number>;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
export interface TaskOutcomeModelSizeReport {
|
|
1390
|
+
dimensions: readonly ['model', 'size'];
|
|
1391
|
+
cells: TaskOutcomeModelSizeCell[];
|
|
1392
|
+
cards: TaskOutcomeModelSizeCard[];
|
|
1393
|
+
/** 축별 «못 읽음» 수 — 카드 합계가 아니라 축마다 센다(한 카드가 둘을 못 읽을 수 있다). */
|
|
1394
|
+
unknown: { model: number; tier: number; size: number };
|
|
1395
|
+
samples: number;
|
|
1396
|
+
limitReached: boolean;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const OUTCOME_MODEL_SIZE_RESULTS: readonly TaskOutcomeModelSizeResult[] = ['landed', 'scope-escape', 'convergence-failure', 'no-result-line'];
|
|
1400
|
+
|
|
1401
|
+
/** `--by` 인자 — 지금은 `model,size` 하나뿐이다(순서 무관 · 그 밖은 거부). */
|
|
1402
|
+
function parseOutcomeDimensions(value: string | undefined): readonly ['model', 'size'] | undefined {
|
|
1403
|
+
if (value === undefined) return undefined;
|
|
1404
|
+
const parts = [...new Set(value.split(',').map((part) => part.trim().toLowerCase()).filter(Boolean))];
|
|
1405
|
+
if (parts.length !== TASK_OUTCOME_BY_DIMENSIONS.length || !TASK_OUTCOME_BY_DIMENSIONS.every((dimension) => parts.includes(dimension))) {
|
|
1406
|
+
throw new Error(`--by 는 ${TASK_OUTCOME_BY_DIMENSIONS.join(',')} 다(모델×조각 크기): ${value}`);
|
|
1407
|
+
}
|
|
1408
|
+
return ['model', 'size'];
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/** 카드 문면의 «바꿀 동작 수» — `바꿀` 선언 줄 ⊕ 그 아래 항목. 선언이 없으면 null(0 으로 지어내지 않는다). */
|
|
1412
|
+
function declaredChangeCount(text: string): number | null {
|
|
1413
|
+
let declared = 0;
|
|
1414
|
+
let explicitlyNone = false;
|
|
1415
|
+
let insideChangeList = false;
|
|
1416
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1417
|
+
const trimmed = line.trim();
|
|
1418
|
+
if (/^바꿀(?:\s|:|것|동작)/.test(trimmed)) {
|
|
1419
|
+
const at = trimmed.indexOf(':');
|
|
1420
|
+
const inline = at < 0 ? '' : trimmed.slice(at + 1).trim();
|
|
1421
|
+
if (/^바꿀\s*(?:것|동작)?\s*:?[\s]*(?:없음|없다)$/u.test(trimmed)) {
|
|
1422
|
+
explicitlyNone = true;
|
|
1423
|
+
insideChangeList = false;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
// «바꿀 것: 하나» 처럼 한 줄에 적었으면 그 줄이 한 동작이고, 목록이 뒤따르지 않는다.
|
|
1427
|
+
// «바꿀 것: 다음» 처럼 목록을 가리키는 선언은 그 줄이 동작이 아니고, 뒤따르는 목록을 센다.
|
|
1428
|
+
insideChangeList = inline.length === 0 || /^다음[.!?]?$/.test(inline);
|
|
1429
|
+
if (!insideChangeList) declared++;
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (!insideChangeList) continue;
|
|
1433
|
+
if (/^[-*·]\s+\S/.test(trimmed)) { declared++; continue; }
|
|
1434
|
+
if (trimmed.length > 0) insideChangeList = false;
|
|
1435
|
+
}
|
|
1436
|
+
return declared > 0 ? declared : explicitlyNone ? 0 : null;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/** 조각 크기 — 대상 경로 수(카드가 적은 목록) ⊕ 바꿀 동작 수(문면의 바꿀 선언). */
|
|
1440
|
+
function outcomeCardSize(card: TaskCard | undefined): TaskOutcomeModelSizeCard['size'] {
|
|
1441
|
+
const targetPaths = Array.isArray(card?.targetPaths) ? card.targetPaths.length : TASK_OUTCOME_UNKNOWN;
|
|
1442
|
+
const behaviors = card && typeof card.text === 'string' ? declaredChangeCount(card.text) ?? TASK_OUTCOME_UNKNOWN : TASK_OUTCOME_UNKNOWN;
|
|
1443
|
+
return { targetPaths, behaviors };
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function outcomeCardModel(card: TaskCard | undefined): string {
|
|
1447
|
+
const llm = card?.childLlm;
|
|
1448
|
+
if (!llm || typeof llm.provider !== 'string' || !llm.provider || typeof llm.model !== 'string' || !llm.model) return TASK_OUTCOME_UNKNOWN;
|
|
1449
|
+
return `${llm.provider}/${llm.model}`;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function outcomeCardTier(card: TaskCard | undefined): string {
|
|
1453
|
+
const tier = card?.tier;
|
|
1454
|
+
return tier === '하' || tier === '중' || tier === '상' || tier === '특' ? tier : TASK_OUTCOME_UNKNOWN;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* 결과 판정 — 카드 원장에 있는 사실만 쓴다(문면 추측 없음).
|
|
1459
|
+
* ① 실제 실행·성공한 손 범위 자르기 hand-move(trim-scope)가 있으면 «범위 이탈» — 실행·성공 값이
|
|
1460
|
+
* 없거나 false인 조정 · post-land partial(남은 작업)만으로는 범위 이탈이 아니다.
|
|
1461
|
+
* ② PR MERGED 종결 증거(row.pr ⊕ 원장 prTerminal) 또는 손 착지 종결 기록(closed-by-hand)이 있으면 «PR 착지» —
|
|
1462
|
+
* 카드 상태(landed·done)만으로는 착지로 단정하지 않는다.
|
|
1463
|
+
* ③ 실패 종결 줄이 있으면 «수렴 실패» ④ 그 밖은 «결과 줄 없음».
|
|
1464
|
+
*/
|
|
1465
|
+
function classifyOutcomeResult(card: TaskCard | undefined, row: TaskOutcomeRow): TaskOutcomeModelSizeResult {
|
|
1466
|
+
if ((card?.history ?? []).some((item) => item.event === HAND_MOVE_HISTORY_EVENT && item.kind === 'trim-scope'
|
|
1467
|
+
&& item.executed === true && item.ok === true)) return 'scope-escape';
|
|
1468
|
+
const terminal = card ? boundPrTerminal(card) : undefined;
|
|
1469
|
+
const landedByHand = row.cardStatus === 'landed-by-hand'
|
|
1470
|
+
&& (card?.history ?? []).some((item) => item.event === CLOSED_BY_HAND_EVENT);
|
|
1471
|
+
if (row.pr?.state === 'MERGED' || terminal?.state === 'MERGED' || landedByHand) return 'landed';
|
|
1472
|
+
const terminalLine = card ? outcomeTerminalHistoryLine(card) : undefined;
|
|
1473
|
+
if (terminalLine?.event === 'failed' || terminalLine?.event === 'launch-failed') return 'convergence-failure';
|
|
1474
|
+
if (row.cardStatus === 'failed' || row.cardStatus === 'launch-failed') return 'convergence-failure';
|
|
1475
|
+
return 'no-result-line';
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* MODEL-SIZE-OUTCOME-MEASURE — `tasks outcomes --by model,size`(읽기 전용).
|
|
1480
|
+
* 창 안에서 «끝난» 카드(readTaskOutcomes 와 같은 판정)를 모델×조각 크기 칸으로 묶어 성공률·표본 수·unknown 수를 낸다.
|
|
1481
|
+
* `report` 와 `snapshot` 을 함께 주면 같은 원장 시점으로 창·크기·결과를 센다.
|
|
1482
|
+
*/
|
|
1483
|
+
export function readTaskOutcomeModelSize(
|
|
1484
|
+
window: { sinceMs?: number; untilMs?: number } = {},
|
|
1485
|
+
deps: Pick<TasksCliDeps, 'taskStatePath' | 'outcomesLimit'> = {},
|
|
1486
|
+
report?: Omit<TaskOutcomeReport, 'since' | 'until'>,
|
|
1487
|
+
snapshot?: Record<string, TaskCard>,
|
|
1488
|
+
): TaskOutcomeModelSizeReport {
|
|
1489
|
+
const cards = snapshot ?? readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {};
|
|
1490
|
+
const observed = report ?? readTaskOutcomes(window, deps, cards);
|
|
1491
|
+
const unknown = { model: 0, tier: 0, size: 0 };
|
|
1492
|
+
const cells = new Map<string, TaskOutcomeModelSizeCell>();
|
|
1493
|
+
// 종단 사건 행은 카드·PR로 둘일 수 있어도 모델×크기 표본은 카드 한 장이다.
|
|
1494
|
+
const seenCards = new Set<string>();
|
|
1495
|
+
const rows = observed.rows.filter((row) => {
|
|
1496
|
+
if (seenCards.has(row.card)) return false;
|
|
1497
|
+
seenCards.add(row.card);
|
|
1498
|
+
return true;
|
|
1499
|
+
}).map((row): TaskOutcomeModelSizeCard => {
|
|
1500
|
+
const card = cards[row.card];
|
|
1501
|
+
const model = outcomeCardModel(card);
|
|
1502
|
+
const tier = outcomeCardTier(card);
|
|
1503
|
+
const size = outcomeCardSize(card);
|
|
1504
|
+
const result = classifyOutcomeResult(card, row);
|
|
1505
|
+
if (model === TASK_OUTCOME_UNKNOWN) unknown.model++;
|
|
1506
|
+
if (tier === TASK_OUTCOME_UNKNOWN) unknown.tier++;
|
|
1507
|
+
const sizeKey = size.targetPaths === TASK_OUTCOME_UNKNOWN || size.behaviors === TASK_OUTCOME_UNKNOWN
|
|
1508
|
+
? TASK_OUTCOME_UNKNOWN : `paths=${size.targetPaths}·behaviors=${size.behaviors}`;
|
|
1509
|
+
if (sizeKey === TASK_OUTCOME_UNKNOWN) unknown.size++;
|
|
1510
|
+
// Cell unknown belongs to its two grouping axes; tier is reported separately per card and in unknown.tier.
|
|
1511
|
+
const unresolved = model === TASK_OUTCOME_UNKNOWN || sizeKey === TASK_OUTCOME_UNKNOWN;
|
|
1512
|
+
const key = `${model}\u0000${sizeKey}`;
|
|
1513
|
+
const cell = cells.get(key) ?? { model, size: sizeKey, samples: 0, landed: 0, successRate: null, unknown: 0,
|
|
1514
|
+
results: Object.fromEntries(OUTCOME_MODEL_SIZE_RESULTS.map((name) => [name, 0])) as Record<TaskOutcomeModelSizeResult, number> };
|
|
1515
|
+
cell.samples++;
|
|
1516
|
+
if (result === 'landed') cell.landed++;
|
|
1517
|
+
if (unresolved) cell.unknown++;
|
|
1518
|
+
cell.results[result]++;
|
|
1519
|
+
cell.successRate = cell.landed / cell.samples;
|
|
1520
|
+
cells.set(key, cell);
|
|
1521
|
+
return { card: row.card, model, tier, size, result };
|
|
1522
|
+
});
|
|
1523
|
+
return {
|
|
1524
|
+
dimensions: ['model', 'size'],
|
|
1525
|
+
cells: [...cells.values()].sort((a, b) => a.model.localeCompare(b.model) || a.size.localeCompare(b.size)),
|
|
1526
|
+
cards: rows,
|
|
1527
|
+
unknown,
|
|
1528
|
+
samples: rows.length,
|
|
1529
|
+
limitReached: observed.limitReached,
|
|
909
1530
|
};
|
|
910
|
-
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
export function registerTasksCommands(program: Command, deps: TasksCliDeps = {}): void {
|
|
1534
|
+
const ctx = createTasksCliContext(deps);
|
|
1535
|
+
const { out, authenticate } = ctx;
|
|
1536
|
+
const tasks = registerTasksRoot(program);
|
|
1537
|
+
|
|
1538
|
+
tasks.command('work-order').description('TASK-AGENT 작업 지시서')
|
|
1539
|
+
.command('build')
|
|
1540
|
+
.description('체크리스트 칸으로 작업 지시서를 만들어 파일에 쓴다')
|
|
1541
|
+
.requiredOption('--checklist <cellId>', '체크리스트 칸 id')
|
|
1542
|
+
.requiredOption('--version <version>', '체크리스트 판')
|
|
1543
|
+
.requiredOption('--out <path>', '작업 지시서 출력 파일')
|
|
1544
|
+
.action((opts: { checklist: string; version: string; out: string }) => {
|
|
1545
|
+
try {
|
|
1546
|
+
validateVersion(opts.version);
|
|
1547
|
+
const item = (deps.workOrderChecklist ?? listChecklist)(opts.version).items.find((cell) => cell.id === opts.checklist);
|
|
1548
|
+
if (!item) throw new Error(`체크리스트 칸 없음: ${opts.checklist}`);
|
|
1549
|
+
const targets = /^대상 경로:[ \t]*(.*)$/.exec(item.title.split(/\r?\n/, 1)[0]!)?.[1]?.split(' · ').map((path) => path.trim()).filter(Boolean) ?? [];
|
|
1550
|
+
const missing = targets.filter((path) => !existsSync(resolve(process.cwd(), path)));
|
|
1551
|
+
const boundary = [
|
|
1552
|
+
...(targets.length ? [`대상 경로: ${targets.join(' · ')}`] : []),
|
|
1553
|
+
...(missing.length ? [`boundary.missing: ${missing.join(' · ')}`] : []),
|
|
1554
|
+
].join('\n');
|
|
1555
|
+
const order = buildWorkOrder({
|
|
1556
|
+
cellId: item.id,
|
|
1557
|
+
version: opts.version,
|
|
1558
|
+
priority: item.priority,
|
|
1559
|
+
boundary: boundary || undefined,
|
|
1560
|
+
});
|
|
1561
|
+
mkdirSync(dirname(opts.out), { recursive: true });
|
|
1562
|
+
writeFileSync(opts.out, `${JSON.stringify(order, null, 2)}\n`);
|
|
1563
|
+
out(opts.out);
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
out(error instanceof Error ? error.message : '작업 지시서 작성 실패');
|
|
1566
|
+
process.exitCode = 1;
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
911
1569
|
|
|
912
1570
|
tasks.command('green-sweep')
|
|
913
1571
|
.description('병합된 과제 PR 과 칸 근거를 대조해 green/기록만/건너뜀을 미리 본다 (항상 dry-run)')
|
|
@@ -923,6 +1581,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
923
1581
|
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 }));
|
|
924
1582
|
const cards = Object.values(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {});
|
|
925
1583
|
let holds: Set<string> | null = null;
|
|
1584
|
+
let holdDiagnostic: string | undefined;
|
|
926
1585
|
if (opts.traffic) {
|
|
927
1586
|
try {
|
|
928
1587
|
const traffic: unknown = JSON.parse(readFileSync(opts.traffic, 'utf8'));
|
|
@@ -930,8 +1589,13 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
930
1589
|
&& Array.isArray((traffic as { hold?: unknown }).hold)
|
|
931
1590
|
&& (traffic as { hold: unknown[] }).hold.every((id) => typeof id === 'string'))
|
|
932
1591
|
holds = new Set((traffic as { hold: string[] }).hold);
|
|
933
|
-
|
|
934
|
-
|
|
1592
|
+
else holdDiagnostic = `invalid-traffic-shape: ${opts.traffic} must contain hold: string[]`;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
const code = error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
|
|
1595
|
+
? error.code : error instanceof SyntaxError ? 'invalid-json' : 'read-error';
|
|
1596
|
+
holdDiagnostic = `${code}: ${opts.traffic}`;
|
|
1597
|
+
}
|
|
1598
|
+
} else holdDiagnostic = 'traffic-path-not-provided: pass --traffic ~/elanous-hq/seat-state/OP/traffic.json';
|
|
935
1599
|
const prCache = new Map<number, GreenSweepPr | null>();
|
|
936
1600
|
const ambiguousPrs = ambiguousGreenSweepPrs(snapshot.items, opts.version, cards);
|
|
937
1601
|
const origin = run('git', ['remote', 'get-url', 'origin']);
|
|
@@ -981,7 +1645,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
981
1645
|
return check.status === 0 ? true : check.status === 1 ? false : null;
|
|
982
1646
|
})();
|
|
983
1647
|
const decision = decideGreenSweep({ item, candidate, candidateCount: candidates.length,
|
|
984
|
-
pr: landingPr ?? null, ancestor, hold: holds === null ? null : holds.has(item.id), repositoryMatches,
|
|
1648
|
+
pr: landingPr ?? null, ancestor, hold: holds === null ? null : holds.has(item.id), holdDiagnostic, repositoryMatches,
|
|
985
1649
|
ambiguousAcrossCells: ambiguousPrs.has(candidate.pr) });
|
|
986
1650
|
observeGreenSweep(decision);
|
|
987
1651
|
return decision;
|
|
@@ -993,8 +1657,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
993
1657
|
if (opts.json) out(JSON.stringify({ dryRun: true, version: opts.version, counts, decisions }));
|
|
994
1658
|
else {
|
|
995
1659
|
out(`dry-run · ${opts.version} · 후보 ${counts.candidate} · green ${counts.green} · 기록만 ${counts.recordOnly} · 건너뜀 ${counts.skipped}`);
|
|
996
|
-
for (const d of decisions) out(`${d.verdict}\t${d.cell}\t#${d.pr}\t${d.mergeSha ?? '-'}\t${d.reason}\t${d.evidence}\tbefore=${JSON.stringify(d.existingEvidence)}\tafter=${JSON.stringify(d.plannedEvidence)}`);
|
|
997
|
-
if (holds === null) out('⚠ hold 원장을 읽지 못해 green 없이 기록만 판정');
|
|
1660
|
+
for (const d of decisions) out(`${d.verdict}\t${d.cell}\t#${d.pr}\t${d.mergeSha ?? '-'}\t${d.reason}\t${d.evidence}\tbefore=${JSON.stringify(d.existingEvidence)}\tafter=${JSON.stringify(d.plannedEvidence)}\tholdDiagnostic=${d.holdDiagnostic ?? ''}`);
|
|
998
1661
|
}
|
|
999
1662
|
} catch (error) { out(`green-sweep 조회 실패: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1000
1663
|
});
|
|
@@ -1051,39 +1714,75 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1051
1714
|
} catch (error) { out(error instanceof Error ? error.message : '커버 조회 실패'); process.exitCode = 1; }
|
|
1052
1715
|
});
|
|
1053
1716
|
|
|
1054
|
-
tasks.command('
|
|
1055
|
-
.description('
|
|
1056
|
-
.
|
|
1057
|
-
.option('--
|
|
1717
|
+
tasks.command('repair-outcomes')
|
|
1718
|
+
.description('TA 카드 원장의 repair-result 집계(읽기 전용 · 기간 내 runId별 최신 결과, 기간 밖의 정정은 제외)')
|
|
1719
|
+
.requiredOption('--since <iso>', '시작 시각(ISO, 포함)')
|
|
1720
|
+
.option('--until <iso>', '끝 시각(ISO, 미포함 · 기본 현재)')
|
|
1058
1721
|
.option('--json', 'JSON 출력')
|
|
1059
|
-
.action(
|
|
1722
|
+
.action((opts: { since: string; until?: string; json?: boolean }) => {
|
|
1060
1723
|
try {
|
|
1061
|
-
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
if (!
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const data = await responseJson(detail) as { task?: TaskRow };
|
|
1075
|
-
if (!data?.task || data.task.id !== card.id) throw new Error('넥서스 태스크 상세 형식이 유효하지 않다');
|
|
1076
|
-
if (!opts.provider || (data.task.generatedBy?.kind === 'external' && data.task.generatedBy.provider === opts.provider)) rows.push(data.task);
|
|
1077
|
-
}
|
|
1078
|
-
rows.sort(taskOrder);
|
|
1079
|
-
if (opts.json) out(JSON.stringify(rows));
|
|
1080
|
-
else {
|
|
1081
|
-
out('id\t우선순위\t상태\t승인\t출처\t제목');
|
|
1082
|
-
for (const row of rows) out(taskLine(row));
|
|
1083
|
-
}
|
|
1084
|
-
} catch (error) { handleError(error, out); }
|
|
1724
|
+
const since = Date.parse(opts.since);
|
|
1725
|
+
const until = opts.until === undefined ? Date.now() : Date.parse(opts.until);
|
|
1726
|
+
const valid = (value: string) => /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d\d:\d\d)$/.test(value);
|
|
1727
|
+
if (!valid(opts.since) || !Number.isFinite(since) || opts.until !== undefined && (!valid(opts.until) || !Number.isFinite(until)) || since >= until)
|
|
1728
|
+
throw new Error('--since/--until 는 순서가 맞는 ISO 시각이어야 한다');
|
|
1729
|
+
const path = deps.taskStatePath ?? taskStatePathDefault();
|
|
1730
|
+
if (!existsSync(path)) throw new Error('TA 카드 원장 없음 — 수리 0건으로 판정 불가');
|
|
1731
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(path).tasks;
|
|
1732
|
+
if (!tasks || typeof tasks !== 'object' || Array.isArray(tasks)) throw new Error('TA 카드 원장 tasks 없음 — 수리 0건으로 판정 불가');
|
|
1733
|
+
const report = measureRepairOutcomes(Object.values(tasks), { since, until });
|
|
1734
|
+
if (opts.json) out(JSON.stringify(report));
|
|
1735
|
+
else out(`수리 결과(카드 원장) ${report.applied} 적용 · ${report.failed} 실패 · ${report.unconfirmed} 미확정 · 카드 ${report.cards} · 결과 ${report.results} · runId 결손 ${report.missingRunId} · 미지 효과 ${report.unknownEffect} (기간 내 최신 결과 · 판단 로그 아님)`);
|
|
1736
|
+
} catch (error) { out(error instanceof Error ? error.message : '수리 결과 원장 읽기 실패'); process.exitCode = 1; }
|
|
1085
1737
|
});
|
|
1086
1738
|
|
|
1739
|
+
tasks.command('outcomes')
|
|
1740
|
+
.description('TA 카드·PR 종결 사건을 별도 분모로 센다 — 결속 자식 원장·종료 꼬리 원문과 부재 이유 포함(읽기 전용 · 사건 페이지 상한)')
|
|
1741
|
+
.option('--since <기간>', '시작 시각 — ISO 또는 상대 기간(24h · 3d)')
|
|
1742
|
+
.option('--until <기간>', '끝 시각 — ISO 또는 지금에서 뺀 상대 기간(24h · 3d)')
|
|
1743
|
+
.option('--by <축>', '집계 축 — card-terminal | pr-terminal | model,size(읽기 전용)')
|
|
1744
|
+
.option('--json', 'JSON 출력(기본값)')
|
|
1745
|
+
.action((opts: { since?: string; until?: string; by?: string; json?: boolean }) => {
|
|
1746
|
+
try {
|
|
1747
|
+
const now = deps.outcomesNow?.() ?? Date.now();
|
|
1748
|
+
const sinceMs = parseOutcomeTime(opts.since, '--since', now);
|
|
1749
|
+
const untilMs = parseOutcomeTime(opts.until, '--until', now) ?? now;
|
|
1750
|
+
// 창이 뒤집혔으면 아무것도 읽지 않는다(«빈 결과»로 속이지 않는다).
|
|
1751
|
+
if (sinceMs !== undefined && untilMs !== undefined && sinceMs > untilMs) throw new Error('--since 가 --until 보다 늦다');
|
|
1752
|
+
// 축 이름이 틀렸으면 원장도 읽지 않는다(지어낸 집계를 내지 않게).
|
|
1753
|
+
const terminalKind = opts.by === 'card-terminal' ? 'card' : opts.by === 'pr-terminal' ? 'pr' : undefined;
|
|
1754
|
+
const dimensions = terminalKind ? null : parseOutcomeDimensions(opts.by);
|
|
1755
|
+
const snapshot = dimensions
|
|
1756
|
+
? readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {}
|
|
1757
|
+
: undefined;
|
|
1758
|
+
const observed = readTaskOutcomes({ sinceMs, untilMs, terminalKind }, deps, snapshot);
|
|
1759
|
+
const report: TaskOutcomeReport = {
|
|
1760
|
+
since: sinceMs === undefined ? null : new Date(sinceMs).toISOString(),
|
|
1761
|
+
until: untilMs === undefined ? null : new Date(untilMs).toISOString(),
|
|
1762
|
+
...observed,
|
|
1763
|
+
};
|
|
1764
|
+
try { debug.log('task-agent', 'card-outcomes', { since: report.since, until: report.until, rows: report.rows.length,
|
|
1765
|
+
missingReason: report.aggregate.missingReason, stopReason: report.stopReason,
|
|
1766
|
+
childLedgerObserved: report.childLedgerObserved, reasonObserved: report.reasonObserved }); } catch { /* fail-soft */ }
|
|
1767
|
+
// `--by model,size` 는 지금 JSON 에 «한 칸»을 더할 뿐 — 없으면 종전 JSON 그대로다.
|
|
1768
|
+
out(JSON.stringify(dimensions ? { ...report, byModelSize: readTaskOutcomeModelSize({ sinceMs, untilMs }, deps, observed, snapshot) } : report));
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
out(JSON.stringify({ error: error instanceof Error ? error.message : '결과 조회 실패' }));
|
|
1771
|
+
process.exitCode = 1;
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
tasks.command('doctor <cardIdOrRunId>')
|
|
1776
|
+
.description('카드·런의 재발사 조건과 출처를 읽기 전용으로 판정한다')
|
|
1777
|
+
.option('--traffic <path>', 'hold 원장 traffic.json — 생략·판독 실패 시 hold=unknown')
|
|
1778
|
+
.option('--json', '한 줄 JSON 출력(기본값)')
|
|
1779
|
+
.action((target: string, opts: { traffic?: string; json?: boolean }) => {
|
|
1780
|
+
try { out(JSON.stringify(readTaskDoctor(target, { traffic: opts.traffic }, deps))); }
|
|
1781
|
+
catch (error) { out(JSON.stringify({ error: error instanceof Error ? error.message : 'doctor 조회 실패' })); process.exitCode = 1; }
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
registerTaskListCommand(tasks, ctx);
|
|
1785
|
+
|
|
1087
1786
|
tasks.command('hand [text]')
|
|
1088
1787
|
.description('TASK-AGENT 에 과제 한 줄을 넘긴다 — 카드를 적고 첫 수(harness say --substrate pod --merge-by-host)를 고른다 · 기본 shadow(발사 0) · `--mission` 이면 조각 카드 ⊕ 선후로 쪼개 선행 없는 조각만 넘긴다')
|
|
1089
1788
|
.addOption(new Option('--seat <seat>', `과제 자리 (${TASK_SEATS.join('|')})`).choices([...TASK_SEATS]))
|
|
@@ -1099,10 +1798,12 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1099
1798
|
.option('--ta-land', 'TA-LIVE-LAND-2 — 호스트는 재게이트만 하고 병합은 TASK-AGENT 의 propose-land(`pr land --expected-head`)에 맡긴다 · 카드 taLand ⊕ 발사 env ELANOUS_TA_LAND=1 · taskAgent.liveMoves 에 propose-land 가 없으면 종전대로 호스트가 병합')
|
|
1100
1799
|
.option('--child-llm <provider/model>', 'TASKS-HAND-CHILD-LLM — 구현 자식 LLM(예: grok/grok-4.7) → 발사 인자 --child-llm-provider <provider> --child-llm-model <model> · «첫» `/` 에서만 가른다(모델 id 의 `/` 는 보존) · openrouter 는 모델 id 가 openrouter/ 접두를 포함하므로 전체를 모델로(openrouter/z-ai/glm-5.3 → --child-llm-model openrouter/z-ai/glm-5.3) · 카드 childLlm · 안 주면 명령·카드 무변경')
|
|
1101
1800
|
.option('--child-llm-effort <level>', '구현 자식 추론 노력 → 발사 인자 --child-llm-effort(검증은 하니스 · --child-llm 과 함께)')
|
|
1801
|
+
.option('--role-llm <role=provider[/tier]>', 'TA 부모 역할 LLM(반복 가능 · planning|classify|audit|review만). 정확한 모델은 role={"provider":"openrouter","model":"openrouter/z-ai/glm-5.3"}; 자식 모델은 --child-llm', (value: string, previous: string[] = []) => [...previous, value])
|
|
1102
1802
|
.addOption(new Option('--tier <level>', '디스패처 난이도(하|중|상|특) — 카드에 보존해 수리 모델 정책에 사용').choices(['하', '중', '상', '특']))
|
|
1103
1803
|
.option('--tag <k=v>', 'RUN-IDENTITY-TAGS — 출생 신분을 덮는다(class|care|rights|protect 의 k=v · 반복: --tag class=gate --tag protect=true) — 카드 원장 identity 에 남는다', (value: string, previous: string[] | undefined) => [...(previous ?? []), value])
|
|
1804
|
+
.option('--draft-claim <pr>', 'DRAFT-CLAIM-AUTO-2-FU — PR 를 관측(gh pr view)해 draftClaim 을 조립해 넘긴다 · 관측한 PR 머리에서 `elanous self review <pr> --json` 로 손 수확을 먼저 확정한다(확정 못 하면 claim 문면을 만들지 않는다) · claim 문면을 실제 PR 댓글로 게시(gh pr comment)한다 · 같은 PR 의 살아 있는 claim 기록(카드 draftClaim)이 있으면 거부한다')
|
|
1104
1805
|
.option('--json', 'JSON 출력')
|
|
1105
|
-
.action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; version?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; tier?: TaskCard['tier']; tag?: string[]; json?: boolean }) => {
|
|
1806
|
+
.action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; version?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; roleLlm?: string[]; tier?: TaskCard['tier']; tag?: string[]; draftClaim?: string; json?: boolean }) => {
|
|
1106
1807
|
const suppliedText = text !== undefined;
|
|
1107
1808
|
/** RUN-IDENTITY-TAGS — 칸을 찾은 판(출생 신분이 그 판의 칸 우선순위를 읽는다 · 개발 판 밖 칸도). */
|
|
1108
1809
|
let birthBoardVersion: string | undefined;
|
|
@@ -1143,9 +1844,11 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1143
1844
|
return aFuture ? compareVersions(a, b) : compareVersions(b, a);
|
|
1144
1845
|
});
|
|
1145
1846
|
const matches: Array<{ version: string; title: string }> = [];
|
|
1847
|
+
// CLI-ENTRY-LAZY — board(전이 폐포가 가장 크다)는 칸을 찾을 때만 싣는다.
|
|
1848
|
+
const packBoard = deps.releaseBoard ?? (await import('../task-agent/board.js')).releasePackBoard;
|
|
1146
1849
|
for (const version of orderedVersions) {
|
|
1147
1850
|
try {
|
|
1148
|
-
const title = findTitle((
|
|
1851
|
+
const title = findTitle(packBoard(version));
|
|
1149
1852
|
if (title !== undefined) matches.push({ version, title });
|
|
1150
1853
|
} catch (error) {
|
|
1151
1854
|
if (opts.version !== undefined) {
|
|
@@ -1182,13 +1885,14 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1182
1885
|
process.exitCode = 1;
|
|
1183
1886
|
return;
|
|
1184
1887
|
}
|
|
1185
|
-
if (opts.mission !== undefined && (opts.completion !== undefined || opts.project !== undefined || opts.goal !== undefined || opts.milestone !== undefined || opts.taLand === true || opts.childLlm !== undefined || opts.childLlmEffort !== undefined || opts.tier !== undefined || opts.tag !== undefined)) {
|
|
1186
|
-
out('--completion/--project/--target/--goal/--milestone/--ta-land/--child-llm/--tier/--tag 는 과제 한 줄에만 준다 — --mission 조각은 종전처럼 code-pr 이다');
|
|
1888
|
+
if (opts.mission !== undefined && (opts.completion !== undefined || opts.project !== undefined || opts.goal !== undefined || opts.milestone !== undefined || opts.taLand === true || opts.childLlm !== undefined || opts.childLlmEffort !== undefined || opts.roleLlm !== undefined || opts.tier !== undefined || opts.tag !== undefined)) {
|
|
1889
|
+
out('--completion/--project/--target/--goal/--milestone/--ta-land/--child-llm/--role-llm/--tier/--tag 는 과제 한 줄에만 준다 — --mission 조각은 종전처럼 code-pr 이다');
|
|
1187
1890
|
process.exitCode = 1;
|
|
1188
1891
|
return;
|
|
1189
1892
|
}
|
|
1190
1893
|
if (opts.mission !== undefined) {
|
|
1191
1894
|
try {
|
|
1895
|
+
const { handMission } = await import('../task-agent/mission.js');
|
|
1192
1896
|
const result = await handMission({
|
|
1193
1897
|
text: opts.mission,
|
|
1194
1898
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
@@ -1227,7 +1931,61 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1227
1931
|
if (resolved.kind === 'inlined') {
|
|
1228
1932
|
try { debug.log('task-agent.hand', 'goal-file-inlined', { path: resolved.path, chars: resolved.chars, sha256: resolved.sha256 }); } catch { /* fail-soft */ }
|
|
1229
1933
|
}
|
|
1230
|
-
|
|
1934
|
+
if (opts.live && opts.checklist && birthBoardVersion) {
|
|
1935
|
+
const origin = deps.handOriginRepo?.() ?? execFileSync('git', ['remote', 'get-url', 'origin'],
|
|
1936
|
+
{ cwd: process.cwd(), encoding: 'utf8' }).trim().replace(/\.git\/?$/, '');
|
|
1937
|
+
const repo = origin.match(/^(?:git@github\.com:|https:\/\/github\.com\/|ssh:\/\/git@github\.com\/)?([^/\s]+\/[^/\s]+)\/?$/)?.[1];
|
|
1938
|
+
if (!repo) throw new Error(`현재 worktree origin이 GitHub 저장소가 아니다: ${origin}`);
|
|
1939
|
+
const run = deps.handOpenPrRun ?? ((command: string, args: string[]) => spawnSync(command, args,
|
|
1940
|
+
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000, maxBuffer: 16 * 1024 * 1024 }));
|
|
1941
|
+
const listed = run('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--limit', '500', '--json', 'number,body']);
|
|
1942
|
+
if (listed.status !== 0) throw new Error(`같은 칸 OPEN PR 조회 실패 — 구현 발사 0: ${opts.checklist}`);
|
|
1943
|
+
let rows: unknown;
|
|
1944
|
+
try { rows = JSON.parse(listed.stdout); } catch { throw new Error(`OPEN PR 목록 해석 실패 — 구현 발사 0: ${opts.checklist}`); }
|
|
1945
|
+
if (!Array.isArray(rows) || rows.length >= 500 || !rows.every((row) =>
|
|
1946
|
+
Number.isSafeInteger(doctorObject(row)?.number) && typeof doctorObject(row)?.body === 'string')) {
|
|
1947
|
+
throw new Error(`OPEN PR 목록 불완전 — 구현 발사 0: ${opts.checklist}`);
|
|
1948
|
+
}
|
|
1949
|
+
const openPrs = rows.filter((row) => cellIdFromBody(doctorObject(row)!.body as string) === opts.checklist)
|
|
1950
|
+
.map((row) => doctorObject(row)!.number as number);
|
|
1951
|
+
if (openPrs.length > 1) throw new Error(`같은 칸 OPEN PR 복수 — 구현 발사 0: ${opts.checklist} ${openPrs.map((pr) => `#${pr}`).join(', ')}`);
|
|
1952
|
+
if (openPrs.length === 1) {
|
|
1953
|
+
const number = openPrs[0]!;
|
|
1954
|
+
const view = (deps.adoptPrView ?? ((pr: number, cwd: string) => JSON.parse(execFileSync('gh',
|
|
1955
|
+
['pr', 'view', String(pr), '--repo', repo, '--json', 'number,url,state,baseRefName,headRefOid'], { cwd, encoding: 'utf8' }))))(number, process.cwd());
|
|
1956
|
+
if (view.number !== number) throw new Error(`조회 PR 번호가 다르다: 요청 #${number} · 응답 #${view.number}`);
|
|
1957
|
+
const viewedRepo = typeof view.url === 'string'
|
|
1958
|
+
? view.url.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)\/?$/) : null;
|
|
1959
|
+
if (view.state !== 'OPEN' || view.baseRefName !== 'main' || !/^[a-f0-9]{40}$/i.test(view.headRefOid)
|
|
1960
|
+
|| viewedRepo?.[1]?.toLowerCase() !== repo.toLowerCase() || Number(viewedRepo?.[2]) !== number) {
|
|
1961
|
+
throw new Error(`OPEN/main PR 신원 불일치: #${number} — 구현 발사 0`);
|
|
1962
|
+
}
|
|
1963
|
+
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
1964
|
+
const boundCards = Object.values(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {})
|
|
1965
|
+
.filter((card) => card.checklistId === opts.checklist && cardPrNumber(card) === number);
|
|
1966
|
+
if (boundCards.length) {
|
|
1967
|
+
const bound = boundCards.length === 1 ? boundCards[0] : undefined;
|
|
1968
|
+
const same = bound?.status === 'launched' && bound.taLand === true
|
|
1969
|
+
&& typeof bound.pr === 'object' && bound.pr.url === view.url
|
|
1970
|
+
&& (!bound.checklistVersion || bound.checklistVersion === birthBoardVersion);
|
|
1971
|
+
if (!same) throw new Error(`같은 칸 OPEN PR #${number}에 기존 카드 ${boundCards.map((card) => card.id).join(', ')}가 있다 — 구현 발사 0; tasks adopt-pr ${number} --card <카드> --version ${birthBoardVersion} 확인`);
|
|
1972
|
+
if (opts.json) out(JSON.stringify({ card: bound.id, pr: number, head: view.headRefOid, launched: false, next: `tasks rejudge ${bound.id}` }));
|
|
1973
|
+
else out(`${bound.id}\tPR #${number} already-bound\t구현 발사 0 · 다음: tasks rejudge ${bound.id}`);
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
const { adoptExistingPr } = await import('../task-agent/adopt-pr.js');
|
|
1977
|
+
const current = (deps.adoptChecklist ?? listChecklist)(birthBoardVersion).items.find((item) => item.id === opts.checklist);
|
|
1978
|
+
if (!current || current.status === 'green' || current.status === 'done') throw new Error(`입양할 미완 칸 없음: ${birthBoardVersion}/${opts.checklist}`);
|
|
1979
|
+
const adopted = adoptExistingPr({ pr: view, repo, statePath,
|
|
1980
|
+
checklist: { version: birthBoardVersion, id: current.id, title: current.title, status: current.status },
|
|
1981
|
+
checklistAtWrite: () => (deps.adoptChecklist ?? listChecklist)(birthBoardVersion).items.find((item) => item.id === opts.checklist) });
|
|
1982
|
+
try { debug.log('task-agent.hand', 'open-pr-adopted', { card: adopted.card.id, pr: number, head: view.headRefOid, launched: false }); } catch { /* fail-soft */ }
|
|
1983
|
+
if (opts.json) out(JSON.stringify({ card: adopted.card.id, pr: number, head: view.headRefOid, launched: false, next: `tasks rejudge ${adopted.card.id}` }));
|
|
1984
|
+
else out(`${adopted.card.id}\tPR #${number} adopted\t구현 발사 0 · 다음: tasks rejudge ${adopted.card.id}`);
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
const handOpts = {
|
|
1231
1989
|
text: resolved.text,
|
|
1232
1990
|
...(opts.project !== undefined && opts.target !== undefined ? { project: { id: opts.project, target: opts.target } } : {}),
|
|
1233
1991
|
...(opts.goal !== undefined ? { goal: opts.goal } : {}),
|
|
@@ -1236,18 +1994,55 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1236
1994
|
...(opts.checklist ? { checklistId: opts.checklist } : {}),
|
|
1237
1995
|
...(opts.completion ? { completion: opts.completion } : {}),
|
|
1238
1996
|
live: opts.live === true,
|
|
1997
|
+
preflightGoalDoneBoundary: opts.live === true && deps.taskLauncher === undefined,
|
|
1239
1998
|
...(opts.taLand === true ? { taLand: true } : {}),
|
|
1240
1999
|
...(opts.childLlm !== undefined ? { childLlm: opts.childLlm } : {}),
|
|
1241
2000
|
...(opts.childLlmEffort !== undefined ? { childLlmEffort: opts.childLlmEffort } : {}),
|
|
2001
|
+
...(opts.roleLlm?.length ? { parentRoleLlm: opts.roleLlm } : {}),
|
|
1242
2002
|
...(opts.tier !== undefined ? { tier: opts.tier } : {}),
|
|
1243
2003
|
...(opts.tag !== undefined ? { tags: opts.tag } : {}),
|
|
1244
2004
|
...(birthBoardVersion !== undefined ? { checklistVersion: birthBoardVersion } : {}),
|
|
1245
2005
|
...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
|
|
1246
2006
|
...(deps.taskCompositionRoot ? { compositionRoot: deps.taskCompositionRoot } : {}),
|
|
1247
2007
|
launcher: deps.taskLauncher ?? defaultTaskLauncher,
|
|
1248
|
-
}
|
|
2008
|
+
};
|
|
2009
|
+
// DRAFT-CLAIM-AUTO-2-FU — 생산 배선: `--draft-claim <pr>` 로 관측·게시·수확 리뷰 포트를 끼워 draftClaim(⊕ 손 수확)을
|
|
2010
|
+
// «조립해» 넘긴다 — handDraftClaim 이 관측한 PR 머리와 리뷰 러너로 수확을 확정한 뒤에만 claim 문면을 게시한다.
|
|
2011
|
+
const draftClaimPr = opts.draftClaim === undefined ? undefined : Number(opts.draftClaim);
|
|
2012
|
+
if (opts.draftClaim !== undefined && (!Number.isInteger(draftClaimPr) || (draftClaimPr ?? 0) <= 0)) {
|
|
2013
|
+
out(`--draft-claim 은 PR 번호다: ${opts.draftClaim}`);
|
|
2014
|
+
process.exitCode = 1;
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (draftClaimPr !== undefined && opts.live && (opts.tier === '상' || opts.tier === '특')) {
|
|
2018
|
+
try { debug.log('task-agent.hand', 'tier-split-bypassed', { reason: 'draft-claim', pr: draftClaimPr, tier: opts.tier }); } catch { /* fail-soft */ }
|
|
2019
|
+
}
|
|
2020
|
+
const handed = draftClaimPr !== undefined
|
|
2021
|
+
? { kind: 'whole' as const, result: await handDraftClaim({ ...handOpts, pr: draftClaimPr, gh: deps.draftClaimGh ?? defaultDraftClaimGhPorts() }) }
|
|
2022
|
+
: await (await import('../task-agent/tier-split.js')).handTaskWithTierSplit({ ...handOpts, ...('tierSizedDecompose' in deps ? { decompose: deps.tierSizedDecompose } : {}) });
|
|
2023
|
+
if (handed.kind === 'split') {
|
|
2024
|
+
// TIER-SPLIT-BEFORE-LAUNCH — 상·특 hand 를 조각으로 나눠 쐈다: 첫 줄 = 묶음 · 조각마다 한 줄(미션 출력과 같은 꼴).
|
|
2025
|
+
if (opts.json) out(JSON.stringify(handed));
|
|
2026
|
+
else {
|
|
2027
|
+
// 디스패처 계약(ta-dispatcher.sh · `grep -E '^ta-' | head -1`): 줄 머리 `ta-` 는 «카드 id» 만 — 조각 카드 줄이 먼저, 묶음 줄은 끝에 `tier-split` 머리로.
|
|
2028
|
+
for (const piece of handed.slices) {
|
|
2029
|
+
out([piece.result?.card.id ?? '-', piece.result?.launched ? 'launched' : piece.error ? 'failed' : 'not launched', `tier ${piece.slice.tier} · 파일 ${piece.slice.files} · 행동 ${piece.slice.behaviours}`, headLine(piece.slice.goal)].join('\t'));
|
|
2030
|
+
if (piece.slice.unavoidable) out(` 불가피: ${piece.slice.unavoidable}`);
|
|
2031
|
+
if (piece.error) out(` ${piece.error}`);
|
|
2032
|
+
}
|
|
2033
|
+
out(`tier-split\t${handed.group}\t${handed.fromTier} → 조각 ${handed.slices.length}`);
|
|
2034
|
+
}
|
|
2035
|
+
if (handed.slices.some((piece) => piece.error)) process.exitCode = 1;
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const result = handed.result;
|
|
2039
|
+
if (handed.kind === 'blocked') process.exitCode = 1;
|
|
1249
2040
|
if (opts.json) out(JSON.stringify(result));
|
|
1250
|
-
else {
|
|
2041
|
+
else if (handed.kind === 'blocked') {
|
|
2042
|
+
// 발사 0 — 디스패처가 «넘김 실패» 로 읽도록 어느 줄도 `ta-` 로 시작하지 않는다.
|
|
2043
|
+
out(`tier-split-failed\t${result.card.id}\tnot launched`);
|
|
2044
|
+
out(`(tier-split-failed — 발사 0 · 카드 failed · ${handed.reason})`);
|
|
2045
|
+
} else {
|
|
1251
2046
|
out(`${result.card.id}\t${result.mode}\t${result.launched ? 'launched' : 'not launched'}`);
|
|
1252
2047
|
if (result.move.command) out(`elanous ${result.move.command.map(shellQuote).join(' ')}`);
|
|
1253
2048
|
else out(result.move.reason);
|
|
@@ -1267,7 +2062,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1267
2062
|
.option('--by <seat>', '바꾼 자리(예: OP·MK·TC·UX) — 안 주면 호출 자리 ELANOUS_TRACK, 그것도 없으면 «unknown» 으로 감사 줄에 적는다(지어내지 않는다)')
|
|
1268
2063
|
.option('--reason <text>', '왜 바꾸나 — 감사 줄에 남긴다')
|
|
1269
2064
|
.option('--json', '바뀐 카드를 JSON 으로 출력')
|
|
1270
|
-
.action((id: string, kv: string, opts: { by?: string; reason?: string; json?: boolean }) => {
|
|
2065
|
+
.action(async (id: string, kv: string, opts: { by?: string; reason?: string; json?: boolean }) => {
|
|
1271
2066
|
try {
|
|
1272
2067
|
const by = opts.by?.trim() || process.env.ELANOUS_TRACK?.trim() || 'unknown';
|
|
1273
2068
|
const card = runTagChange(id, { tag: kv, by, ...(opts.reason !== undefined ? { reason: opts.reason } : {}) }, deps.taskStatePath ?? taskStatePathDefault());
|
|
@@ -1281,6 +2076,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1281
2076
|
return;
|
|
1282
2077
|
}
|
|
1283
2078
|
out(`${card.id}\t${kv}`);
|
|
2079
|
+
const { cardIdentityLines } = await import('./tasks-card-show.js');
|
|
1284
2080
|
for (const line of cardIdentityLines(card)) out(line);
|
|
1285
2081
|
} catch (error) {
|
|
1286
2082
|
out(error instanceof Error ? error.message : '태그 바꾸기 실패');
|
|
@@ -1475,6 +2271,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1475
2271
|
await registerTaskAgentSink(deps.registerSink);
|
|
1476
2272
|
try {
|
|
1477
2273
|
// 내부 팩은 elanous 프로젝트만 낸다 — 다른 프로젝트로 거르면 릴리스 원장을 열지 않는다(그 원장의 오류가 카드 보드를 막지 않게).
|
|
2274
|
+
const { cardsBoard, countBoardTasks, INTERNAL_PACK_PROJECT, mergeBoards, releasePackBoard } = await import('../task-agent/board.js');
|
|
1478
2275
|
const wantsPack = opts.project === undefined || opts.project === INTERNAL_PACK_PROJECT;
|
|
1479
2276
|
const board = mergeBoards(
|
|
1480
2277
|
cardsBoard(deps.taskStatePath ?? taskStatePathDefault()),
|
|
@@ -1507,6 +2304,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1507
2304
|
.option('--json', 'JSON 출력')
|
|
1508
2305
|
.action(async (missionId: string | undefined, opts: { all?: boolean; pr?: Array<[string, string]>; goal?: Array<[string, string]>; live?: boolean; json?: boolean }) => {
|
|
1509
2306
|
await registerTaskAgentSink(deps.registerSink);
|
|
2307
|
+
const { advanceMission, MissionAdvanceError, openMissionIds, recordPieceRef } = await import('../task-agent/mission.js');
|
|
1510
2308
|
const started = Date.now();
|
|
1511
2309
|
if ((missionId === undefined) === (opts.all !== true)) {
|
|
1512
2310
|
const reason = '미션 id «또는» --all 중 하나만 준다';
|
|
@@ -1582,84 +2380,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1582
2380
|
}
|
|
1583
2381
|
});
|
|
1584
2382
|
|
|
1585
|
-
tasks
|
|
1586
|
-
.description(`태스크 상세 보기 (id 가 \`${TASK_CARD_PREFIX}\` 로 시작하면 TASK-AGENT 과제 카드 ⊕ 다음 수)`)
|
|
1587
|
-
.option('--json', 'JSON 출력')
|
|
1588
|
-
.action(async (id: string, opts: { json?: boolean }) => {
|
|
1589
|
-
if (id.startsWith(TASK_CARD_PREFIX)) {
|
|
1590
|
-
await registerTaskAgentSink(deps.registerSink);
|
|
1591
|
-
let card: TaskCard | undefined;
|
|
1592
|
-
try {
|
|
1593
|
-
card = readTaskCard(id, deps.taskStatePath);
|
|
1594
|
-
// 런 id 가 있으면 원장에서 골 id · PR 을 묶는다(찾았을 때만 적는다 · 실패는 그대로 보인다).
|
|
1595
|
-
const bindable = card?.pieces ? card.pieces : card ? [card.id] : [];
|
|
1596
|
-
for (const cardId of bindable) {
|
|
1597
|
-
// HARNESS-PARENT-ON-MSB1: 원격 부모 런은 원장이 그 호스트에 있다 — HQ 거울로 먼저 당긴다(읽기 전용).
|
|
1598
|
-
await mirrorRemoteCardLedger(cardId, deps);
|
|
1599
|
-
try { await replayRemoteCardMoves(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
|
|
1600
|
-
try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
1601
|
-
}
|
|
1602
|
-
try { await refreshCardRunBinding(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
|
|
1603
|
-
// 근거 없음으로 보인다 — 원장 부재와 갈리게 실패는 남긴다.
|
|
1604
|
-
try { debug.log('task-agent', 'card-bind-failed', { card: cardId, via: 'show', error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
1605
|
-
}
|
|
1606
|
-
// TA-REVIEW-RESULT-CAPTURE — 떼어 띄운 live review 의 결과 파일을 이 카드 history 로 회수한다(대기 중인 요청이 있을 때만 쓴다 · 던지지 않는다).
|
|
1607
|
-
captureReviewResults(deps.taskStatePath ?? taskStatePathDefault(), { cardIds: [cardId], ...(deps.reviewResultNow ? { now: deps.reviewResultNow } : {}) });
|
|
1608
|
-
}
|
|
1609
|
-
if (card) card = readTaskCard(id, deps.taskStatePath);
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
|
|
1612
|
-
process.exitCode = 1;
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
if (!card) { out(`과제 카드 없음: ${id}`); process.exitCode = 1; return; }
|
|
1616
|
-
if (card.pieces) {
|
|
1617
|
-
let all: Record<string, TaskCard>;
|
|
1618
|
-
try { all = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {}; } catch (error) {
|
|
1619
|
-
out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
|
|
1620
|
-
process.exitCode = 1;
|
|
1621
|
-
return;
|
|
1622
|
-
}
|
|
1623
|
-
const pieces = card.pieces.map((pieceId) => all[pieceId]).filter((piece): piece is TaskCard => piece !== undefined);
|
|
1624
|
-
const missingPieces = card.pieces.filter((pieceId) => !all[pieceId]);
|
|
1625
|
-
if (opts.json) {
|
|
1626
|
-
out(JSON.stringify({ card, nextMove: nextMoveFor(card), pieces: pieces.map((piece) => ({ card: piece, nextMove: nextMoveFor(piece) })), missingPieces }));
|
|
1627
|
-
if (missingPieces.length) process.exitCode = 1;
|
|
1628
|
-
}
|
|
1629
|
-
else {
|
|
1630
|
-
for (const line of missionLines(card, pieces)) out(line);
|
|
1631
|
-
for (const missing of missingPieces) { out(`조각 카드 없음: ${missing} — 선후 기록이 불완전하다`); process.exitCode = 1; }
|
|
1632
|
-
}
|
|
1633
|
-
return;
|
|
1634
|
-
}
|
|
1635
|
-
// DRAFT-NOT-ARCHIVE: 멈춘 런이 draft PR 대신 남긴 수확 가지(원장 `salvaged`). 못 읽었으면 «못 읽음»(⛔ 없음 아님).
|
|
1636
|
-
let salvage: Awaited<ReturnType<typeof readCardSalvageBranch>> = { salvageBranch: null };
|
|
1637
|
-
try { salvage = await readCardSalvageBranch(card, deps.cardRunEvidence ?? {}); }
|
|
1638
|
-
catch (error) { salvage = { salvageBranch: null, readErrors: [error instanceof Error ? error.message : String(error)] }; }
|
|
1639
|
-
const salvageUnread = !salvage.salvageBranch && !!salvage.readErrors?.length;
|
|
1640
|
-
if (opts.json) out(JSON.stringify({ card, nextMove: nextMoveFor(card), salvageBranch: salvage.salvageBranch, ...(salvageUnread ? { salvageBranchUnread: true } : {}) }));
|
|
1641
|
-
else {
|
|
1642
|
-
for (const line of taskCardLines(card)) out(line);
|
|
1643
|
-
if (card.parentHost) out(`부모 호스트: ${card.parentHost.host}${card.parentHost.pid ? ` (pid ${card.parentHost.pid})` : ''} · PR 까지만 — 병합은 HQ${card.parentHost.log ? ` · 로그 ${card.parentHost.host}:${card.parentHost.log}` : ''}`);
|
|
1644
|
-
if (salvage.salvageBranch) out(`수확 가지: ${salvage.salvageBranch} (draft PR 대신)`);
|
|
1645
|
-
else if (salvageUnread) out('수확 가지: 못 읽음(원장 읽기 실패)');
|
|
1646
|
-
}
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
try {
|
|
1650
|
-
activeToken = undefined;
|
|
1651
|
-
const auth = authenticate();
|
|
1652
|
-
const response = await request(deps, auth, `/v1/tasks/${encodeURIComponent(id)}`);
|
|
1653
|
-
if (!response.ok) throw httpError(response);
|
|
1654
|
-
const data = await responseJson(response) as { task?: TaskRow };
|
|
1655
|
-
if (!data?.task) throw new Error('넥서스 태스크 상세 형식이 유효하지 않다');
|
|
1656
|
-
if (opts.json) out(JSON.stringify(data));
|
|
1657
|
-
else {
|
|
1658
|
-
out(taskLine(data.task));
|
|
1659
|
-
out(JSON.stringify(data, null, 2));
|
|
1660
|
-
}
|
|
1661
|
-
} catch (error) { handleError(error, out); }
|
|
1662
|
-
});
|
|
2383
|
+
registerTaskShowCommand(tasks, ctx);
|
|
1663
2384
|
|
|
1664
2385
|
tasks.command('rejudge [id]')
|
|
1665
2386
|
.description('TA-REJUDGE-ON-HEAD — 런 멈춤 뒤 카드 PR 머리가 바뀌었으면 재게이트(떼어 띄움)·리뷰 재요청·land(재게이트 통과 ⊕ 지금 머리 pass ⊕ must-fix 0 · 겹침 증거)를 다시 판단한다 · 허용은 설정 taskAgent.liveMoves 그대로')
|
|
@@ -1730,7 +2451,9 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1730
2451
|
const reviewBudget = opts.all ? { max: 1, requested: 0 } : undefined;
|
|
1731
2452
|
const landBudget = opts.all ? { max: 1, requested: 0 } : undefined;
|
|
1732
2453
|
const all = [];
|
|
2454
|
+
const { replayRemoteCardMoves } = await import('../task-agent/shadow.js');
|
|
1733
2455
|
for (const cardId of ids) {
|
|
2456
|
+
const { mirrorRemoteCardLedger } = await import('./tasks-card-show.js');
|
|
1734
2457
|
await mirrorRemoteCardLedger(cardId, deps);
|
|
1735
2458
|
try { await replayRemoteCardMoves(cardId, statePath, deps.cardRunEvidence ?? {}); } catch (error) {
|
|
1736
2459
|
try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
@@ -1786,9 +2509,11 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1786
2509
|
.requiredOption('--card <id>').requiredOption('--pr <n>').requiredOption('--head <sha>')
|
|
1787
2510
|
.requiredOption('--repo <root>').requiredOption('--out <path>').requiredOption('--goal-file <path>')
|
|
1788
2511
|
.requiredOption('--run-id <id>').requiredOption('--model-provider <provider>').requiredOption('--model-id <id>')
|
|
2512
|
+
.option('--role-llm <role=provider[/tier]>', 'TA 부모 역할 고정(task hand와 같은 문법 · 반복 가능)', (value: string, previous: string[] = []) => [...previous, value])
|
|
1789
2513
|
.option('--seat <seat>')
|
|
2514
|
+
.option('--move-id <id>', 'HARNESS-FULL-GRAPH-NEXTMOVE — 이 발사를 고른 next-move 결정의 수 id(결과 파일·완료 사건에 그대로 싣는다)')
|
|
1790
2515
|
.action(async (opts: { card: string; pr: string; head: string; repo: string; out: string; goalFile: string;
|
|
1791
|
-
runId: string; modelProvider: string; modelId: string; seat?: string }) => {
|
|
2516
|
+
runId: string; modelProvider: string; modelId: string; seat?: string; moveId?: string; roleLlm?: string[] }) => {
|
|
1792
2517
|
const { readFileSync } = await import('node:fs');
|
|
1793
2518
|
const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
|
|
1794
2519
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
@@ -1797,6 +2522,8 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1797
2522
|
repo: opts.repo, resultPath: opts.out, entry: ELANOUS_CLI_ENTRY,
|
|
1798
2523
|
configDir: getElanousConfigDirOverride() ?? effectiveInstanceRoot(), runId: opts.runId,
|
|
1799
2524
|
model: { provider: opts.modelProvider, model: opts.modelId }, seat: opts.seat,
|
|
2525
|
+
...(opts.roleLlm?.length ? { parentRoleLlm: opts.roleLlm } : {}),
|
|
2526
|
+
...(opts.moveId ? { moveId: opts.moveId } : {}),
|
|
1800
2527
|
goal: readFileSync(opts.goalFile, 'utf8') });
|
|
1801
2528
|
if (!result.ok) process.exitCode = 1;
|
|
1802
2529
|
});
|
|
@@ -1805,7 +2532,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1805
2532
|
.description('외부 태스크 승인')
|
|
1806
2533
|
.action(async (ids: string[]) => {
|
|
1807
2534
|
try {
|
|
1808
|
-
|
|
2535
|
+
ctx.clearToken();
|
|
1809
2536
|
const auth = authenticate();
|
|
1810
2537
|
let failed = false;
|
|
1811
2538
|
for (const id of ids) {
|