@xgent-ai/skills 0.7.5 → 0.7.6
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/.claude/hooks/xgent-statusline.js +32 -18
- package/README.md +61 -35
- package/bin/phoenix-diagram.js +14 -0
- package/bin/xgent-skills.js +132 -240
- package/dist/phoenix-diagram/LICENSE +21 -0
- package/dist/phoenix-diagram/NOTICE.md +34 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/005fc765ddc5115da796cca915baa9557abae13ff35e0a47c47affc56f6c414d.txt +272 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/010492e490b92ec85db01daad2a290ea5959f29314a1c4ff25e7a172e223abf9.txt +18 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f.txt +3 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0218327e7a480793ffdd4eb792379a9709e5c135c7ba267f709d6f6d4d70af0a.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/02bb8c3b4e70190e3986c0404ad2fd8d639b4f534252d82379cc1b502b6d1812.txt +191 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0552c898248eadfc130df0c0e8f6f82776fcb3f1dd5a5af2fa991b5baff9367a.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0ab4d106b6faac07fb6a051815fd1b4d862d730895e2d7d7358c2f13565e7a38.txt +20 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0b28172679e0009b655da42797c03fd163a3379d5cfa67ba1f1655e974a2a1a9.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0d3f7c086c6b6cf3ea4aac714e9aa3e1fb02e355ce35bd21c56acf8d04390885.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/117829c3ca21efb132d81a44b55363d395ab8eea18526873bc828da4c0e5f038.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/123a331b5dbf04c30097fa43b8f858bc85df671fe776de498d01f3d6b7c1f69e.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/1a2f5c12ddc934d58956aa5dbdd3255fe55fd957633ab7d0d39e4f0daa73f7df.txt +26 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5.txt +373 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/1f485f0d6125658861351e3d6084ea90fefe78f89450be9915044b7c56d5a75a.txt +15 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/209fbbe0ad52d9235e37badf9cadfe4dbdc87203179c0899e738b39ade42177b.txt +26 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/20fe7b00e904ed690e3b9fd6073784d3fc428141dbd10b81c01fd143d0797f58.txt +176 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/219920e865eee70b7dcfc948a86b099e7f4fe2de01bcca2ca9a20c0a033f2b59.txt +7 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/23860c2a7b5d96b21569afedf033469bab9fe14a1b24a35068b8641c578ce24d.txt +7 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3.txt +23 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5.txt +220 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5.txt +428 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/29e9fe5074bd27e0e5d5d110394fbbcd841baee2651a3c4b4560a632702cede4.txt +26 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/30fefc3a7d6a0041541858293bcbea2dde4caa4c0a5802f996a7f7e8c0085652.txt +23 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3521672491a3479422d5fe1aca6645dd2984090f85da6e5205abfb18fb7a6897.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/35242e7a83f69875e6edeff02291e688c97caafe2f8902e4e19b49d3e78b4cab.txt +176 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9.txt +23 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/375bdbf54d6584debb4bcc5bce1c9dcfdd581f977c9a8469113dbdf208f4c927.txt +131 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/391a5396cec6230bfabd4ef4eb2350eb895bc5efce377a2218f5702ed020d3e3.txt +20 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/39712155836e5c10ef1b07037b606e4ad2d633e72205d162fdd7e2290aa54f15.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3bb1e548250b3acc43febde65a5cdba9f2e0a3288018aa41f009a3c59ffab51a.txt +390 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3bc70e239e91272782006c638fc0452a714d384224f11f0923036b7be07cf9b5.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3f3d9e0024b1921b067d6f7f88deb4a60cbe7a78e76c64e3f1d7fc3b779b9d04.txt +373 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3f941b3b89cf7b8370ceb83cc76d2120d471b58735d8ca60238a751a48d7f72f.txt +359 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/3fa4ca83dcc9237839b1bdeb2e6d16bdfb5ec0c5ce42b24694d8bbf0dcbef72c.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/42fa16951ce7f24b5a467a40e5b449a1d41e662f97ca779864f053f39e097737.txt +26 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/4455bf75a91154108304cb283e0fea9948c14f13e20d60887cf2552449dea3b1.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/451167c55c0fa447cc2d5632714f5e3c567fe4f1e1badefab2c1333852198aca.txt +542 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/45f522cacecb1023856e46df79ca625dfc550c94910078bd8aec6e02880b3d42.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/45fd05c4865e7c350b98ad7ac50e1b15462d49af4a91e9b0c9dd933dc9a69742.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/4789210f4df8abc03fce1cddd9c2ba517f184840687f2815d741de282c1aa93a.txt +228 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/4cada0bd02ea3692eee6f16400d86c6508bbd3bafb2b65fed0419f36d4f83e8f.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/516b24e051bf5630880ebbd55c40a25ce9552ebaf8970a53e8976eb70e522406.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/553fffcd9b1cb158bc3e9edc35da85ca5c3b3d7d2e61c883ebcfa8a65814b583.txt +13 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/5757a1f28eff735a8b5e7425478f367812d71e974530f5bedd01219480965f4a.txt +51 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a.txt +19 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/5e05b024f653a5ce199e77cbbbd42fb5553562ec714b819421ed0c3e552a75d7.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/60a7062291b01ba068f300612cdbdc20382ac1d4934f07bcdd7167c15299f309.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a.txt +176 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/6df43f6f4b5d4587f3d8d71e45532c688fd168afa5fe89d571cb32fa09c4ef51.txt +187 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/709e3175b4212f7b13aa93971c9f62ff8c69ec45ad8c6532a7e0c41d7a7d6f8c.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/71674605ec4c087fe9eb534e3e4f9e26eb2e4aabcd76a29fd156c6a844d44b3d.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7365cc8878a1d7ce155a58c4ca09c3d7a6be413efa5334a80ea842912b669349.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7abd9b6960dcf7d4d0a48606a5b71bfe37d472db68d70637f3a58a56785f1621.txt +13 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7ca6700600dfa9c9497bf5556365067daa802c871ea78239f129309c7a2048f7.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7cfafc877eccc46c0e346ccbaa5c51bb6b894d2b818e617d970211e232785ad4.txt +15 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c.txt +24 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7e5886959f67f8c75063a9d55cd3cb08c5a8cbea9a944fa1df2d4ae038f53721.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/7fea0ee51a4ca5d5cea7464135fd55e8b09caf3a61da3d451ac8a22af95c033f.txt +20 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/83c1763356e822adde0a2cae748d938a73fdc263849ccff6b27776dff213bd32.txt +24 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/86dd7f026f916daf7511e39951ad8ea8cf55a8db67ae64060dacf829761c18f3.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8a35369f3ca263b3c62fbb5032947e53b6bfebc6c8a4d1bb982de1c069f6fba5.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8b43ce8accd61e9d370b5ca9e9c4f953279b5c239926c62315b40e24df51b726.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8bb1b50b0e5c9399ae33bd35fab2769010fa6c14e8860c729a52295d84896b7a.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8bce3b45e49ecd1461f223b46de133d8f62cd39f745cfdaf81bee554b908bd42.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/8c7516d4b27b1e495be5e38b612298b63de48d05f49cdac94f70f3cd70f8864b.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9046848b63a5c92bff14e4accca80bd987e0623b74adf9226ce5198d312b79d5.txt +30 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/90eb64f0279b0d9432accfa6023ff803bc4965212383697eee27a0f426d5f8d5.txt +12 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9117d922e667125508dde62b02c1f57ed22f5ad21eb536aa2e2d99e1c796e639.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9387c2d197e2370a740ca457dae1662c56de56b811ee4862024343961307f9bf.txt +19 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/946c9835d8034d24404f8cfec5f4654cee5dad17e944afc3d06d742cf2882831.txt +240 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/95bd3988beee069fa2848f648dab43cc6e0b2add2ad6bcb17360caf749802bcc.txt +176 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9ba9550ad48438d0836ddab3da480b3b69ffa0aac7b7878b5a0039e7ab429411.txt +396 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9d185ac6703c4b0453974c0d85e9eee43e6941009296bb1f5eb0b54e2329e9f3.txt +202 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9e0dfd2dd4173a530e238cb6adb37aa78c34c6bc7444e0e10c1ab5d8881f63ba.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9f2ae19ef94ba82887729a4a1617ff25eede56611dd21a4b2c6f797df30fca33.txt +2013 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/9fa38f610a00115dd1f612bdd3559e3b381ea579538b6ff25a5899da250a5fce.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a00511478c83ebb464ef19d840f061c7444adc4636bb561900e75c5808cfc6cc.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499.txt +121 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a5dea80c1f383cb5f80a6bb0da5e55a2beb9f24adb123ce6300af2cbaaa3bf65.txt +19 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a825bd853ab71619a4923d7b4311221427848070ff44d990da39b0b274c1683f.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/ae9baa7beea910273c2f384c2a6b721fb7bd02bda3436074a1072e4ee689f985.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b1181a40b2a7b25cf66fd01481713bc1005df082c53ef73e851e55071b102744.txt +19 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b3d734001a94efff3579978d953391aa7115f877657d25eb54037a43875d078a.txt +9 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b4eb00df6e2a4d22518fcaa6a2b4646f249b3a3c9814509b22bd2091f1392ff1.txt +27 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b68ad1a3367b825447089e1f8d6829b97f47a89eb78d2f4ebaef4672f5606186.txt +22 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/b7e650f3fce5c53249d1cdc608b54df156a97edd636cf9d23498d0cfe7aec63e.txt +19 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c09aae9d3c77b531f56351a9947bc7446511d6b025b3255312d3e3442a9a7583.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c16f8dcf1a368b83be78d826ea23de4079fe1b4469a0ab9ee20563f37ff3d44b.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383.txt +21 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c30152c94a6d75e021adbc52b3a52470366a46edb917e17deae3259251af244c.txt +42 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c394e3bda1f3b9378f30a7cab11242887eeeafc73c7bef36a0fe13e09691e18b.txt +17 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt +201 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c962ee4d1d05ddc138b202b2540219ebc57893fcf97b364852094a9a94ce1365.txt +7 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/c9a75f18b9ab2927829a208fc6aa2cf4e63b8420887ba29cdb265d6619ae82d5.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30.txt +202 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/d1fc1bc0d155df60b2e7705b6b2ae02a05c96f948e1cec6e2fb86360b09f346b.txt +29 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/d5c22aa3118d240e877ad41c5d9fa232f9c77d757d4aac0c2f943afc0a95e0ef.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/d5fca2653c88ad02d197faead77e22b80c70636b5bec6e02f2805f389a21b86d.txt +10 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/db11fec9946737df39ca3898d9cd8c10ec6f6c3a884a6802b0ad0b81b4e8f23a.txt +1 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/dc91f8200e4b2a1f9261035d4c18c33c246911a6c0f7b543d75347e61b249cff.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/ddea4e94a1c35ed9826e54e13a5b50b8f81b21665a00ca07c8861f1859e56b6b.txt +14 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/e271993808fec50ab29350b39539cdec611a9103f827e0aa26d61da70e2d33f8.txt +61 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/e66c269d4819aaab34b49ef5220c4ddab6756f21bb5180761a4eb8561f2b7bbd.txt +438 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/ecc269ef87fd38a1d98e30bfac9ba964a9dbd9315c3770fed98d4d7cb5882055.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/efcfee7981ff72431fffb06925cad00a23dce079ed4354f61030ad5abdb78829.txt +22 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f025ccfb7dfb6bdfedc75ca0f67acc69e6fb4998143d834f7c2f38a29989680f.txt +13 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2.txt +46 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f51ac2c59a222f7476ce507ca879960e2b64ea64bb2786eefdbeb7b0b538d1b7.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f7715d38a3fa1b4ac97c5729740752505a39cb92ee83ab5b102aeb5eaa7cdea4.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f7db81051789b729fea528a63ec4c938fdcb93d9d61d97dc8cc2e9df6d47f2a1.txt +39 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/f9c4c77baa3828004ee54b8a4f2db2e88ed44a6237a493965bf551fac0fcb62d.txt +17 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85.txt +373 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/fb77f0a9c53e473abe5103c8632ef9f0f2874d4fb3f17cb2d8c661aab9cee9d7.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2.txt +25 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/ff92bed461f50338dd703a9ba9aee496a425957873df1d91192776e4bdf5dda7.txt +74 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/COPYRIGHT-library.html +29357 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/Apache-2.0.txt +73 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/BSD-2-Clause.txt +9 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/CC-BY-SA-4.0.txt +427 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/GCC-exception-3.1.txt +30 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/GPL-2.0-only.txt +133 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/GPL-3.0-or-later.txt +202 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/ISC.txt +7 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/LLVM-exception.txt +15 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/MIT.txt +9 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/NCSA.txt +29 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/OFL-1.1.txt +43 -0
- package/dist/phoenix-diagram/THIRD-PARTY-LICENSES/rust-std-1.99.1/licenses/Unicode-3.0.txt +39 -0
- package/dist/phoenix-diagram/THIRD-PARTY-NOTICES.md +210 -0
- package/dist/phoenix-diagram/THIRD-PARTY.json +5459 -0
- package/dist/phoenix-diagram/VERSION.json +39 -0
- package/dist/phoenix-diagram/bundle/ENGINE.json +39 -0
- package/dist/phoenix-diagram/bundle/SKILL.md +109 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.architecture.json +11 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.class.json +23 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.dataflow.json +11 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.erd.json +25 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.lifecycle.json +11 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.sequence.json +13 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.timeline.json +15 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.tree.json +13 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.waterfall.json +12 -0
- package/dist/phoenix-diagram/bundle/examples/minimal.workflow.json +12 -0
- package/dist/phoenix-diagram/bundle/manifest.json +253 -0
- package/dist/phoenix-diagram/bundle/references/architecture-compare.md +111 -0
- package/dist/phoenix-diagram/bundle/references/authoring-defaults.md +42 -0
- package/dist/phoenix-diagram/bundle/references/brand-marks.md +52 -0
- package/dist/phoenix-diagram/bundle/references/compatibility.md +22 -0
- package/dist/phoenix-diagram/bundle/references/delivery-contract.md +127 -0
- package/dist/phoenix-diagram/bundle/references/extending-types.md +14 -0
- package/dist/phoenix-diagram/bundle/references/migration.md +57 -0
- package/dist/phoenix-diagram/bundle/references/preview.md +45 -0
- package/dist/phoenix-diagram/bundle/references/repository-authoring.md +29 -0
- package/dist/phoenix-diagram/bundle/references/viewer-runtime.md +27 -0
- package/dist/phoenix-diagram/bundle/schemas/README.md +23 -0
- package/dist/phoenix-diagram/bundle/schemas/architecture.schema.json +329 -0
- package/dist/phoenix-diagram/bundle/schemas/class.schema.json +348 -0
- package/dist/phoenix-diagram/bundle/schemas/common.schema.json +358 -0
- package/{skills/archify → dist/phoenix-diagram/bundle}/schemas/dataflow.schema.json +36 -8
- package/dist/phoenix-diagram/bundle/schemas/erd.schema.json +384 -0
- package/dist/phoenix-diagram/bundle/schemas/lifecycle.schema.json +202 -0
- package/{skills/archify → dist/phoenix-diagram/bundle}/schemas/sequence.schema.json +41 -10
- package/dist/phoenix-diagram/bundle/schemas/timeline.schema.json +170 -0
- package/dist/phoenix-diagram/bundle/schemas/tree.schema.json +144 -0
- package/dist/phoenix-diagram/bundle/schemas/waterfall.schema.json +150 -0
- package/{skills/archify → dist/phoenix-diagram/bundle}/schemas/workflow.schema.json +42 -10
- package/dist/phoenix-diagram/bundle/scripts/phoenix-bootstrap.cjs +589 -0
- package/dist/phoenix-diagram/bundle/scripts/phoenix-diagram +6 -0
- package/dist/phoenix-diagram/bundle/scripts/phoenix-launcher.cjs +10 -0
- package/dist/phoenix-ui/THIRD-PARTY.json +164 -158
- package/dist/phoenix-ui/VERSION.json +17 -17
- package/dist/phoenix-ui/bundle/blobs/00727f7b333a7be8ac7740aa257a3a3f691db8ae545a6afab2b787acab5e88df +46 -0
- package/dist/phoenix-ui/bundle/blobs/{547e3c89d5b8feeddd67e43131fd84b95253eb42bf7d730b4e471dba011f6e51 → 00f4a6a630f31eb05fbdd5725e4a294233d24574bd70f4f32f4597d8256a7c1c} +0 -4
- package/dist/phoenix-ui/bundle/blobs/{41400e182e2180a567923d58bfa935d2799a54f2a747b2c5813c3884c84f768b → 04057592e18ea9dd3c4edcac14cde3fa5b75239c2552d4459db8e047068f61a8} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{34571f7d46632205d518d15cb008457494dfad745c6b483bb11d63ca3a22ac48 → 0cb3bf26e1980b0e530373fb32edbe5864a171492a04e2abe1952ffe78bff231} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{852d2943678a76c33b2f7a090d2ca6c152cd120e9ae62775fcc91519b9289011 → 0e684572eb98d24b39cd70fa23e66c163178f5a11756dcf8a98fe2335636b33f} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{d17b3a454146e74ef4ecf69d7b72a8fa45c4fe1c695dffb0d36690b653cd2d8f → 10553c45c7bf38e527a57a281c4365152a768b4dd7ef597ff34ae841e9ff7d41} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{e49257c0fd32b195cccb16fbfda769c007be6b38f25aceefecd23cfff9366cec → 125bc7030d7f2ba789d7b1d5f474af34f8902b764e7bc351b98f1f6f55779929} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{74dbcbbad6c3831e95abb38775dd488c59ae9f1d44a95f5fd4bc0051577488fa → 13cf6c054ed424e2eb639ea12d20a4aa2b1c85eeb00afa8cbd8a30c6c22aadd8} +1 -1
- package/dist/phoenix-ui/bundle/blobs/1832ff9624cebd30370eb0e806665c656ac879215f93d33bcc0750baa47459dc +46 -0
- package/dist/phoenix-ui/bundle/blobs/{8e7de5ca347036a07f5f1e9cab942d8ff378d029bc101fa6d126406f3298c9db → 1864a10c29e7062cca5e49d1790dc7d9d8ea777bfa2ab6fd941635965cd0084d} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{784722b72224a7005a80cdcd6408dd2b25b23fd9619d0f6c8788f7ac681fad4c → 1ecc81de39d28b85b95e797bcc338d4213a50231c3315a741d14571fd22c844f} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{b8c614dc7c2eb30ee0f864fec116dbcafb39e5a199dce66d0c1a88b13f76265a → 2079e4146039a8dadf19a39237cbdc0b8b54ea1fa16d320716c2cbf58161c835} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{dba6e4a09c5d752e5ddebe7c2c1aefc9c4b2a84e01a3178599579778ed2d09e9 → 2813aa68c0804a7fe061d88201d8a684cac9987b48511e1e2cbfe0a869a67a82} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{2889c2817b5079abff254bb6d13f92dbde0fabc38be4acc7d20e613eef5e5829 → 2873e340be947c83c3d03520139b73524fd166a34063d2693d3b711021ea5b87} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{64c4aa2d1c24ae5028e987498ac544020d48600a51d9654b2df562681ba480f2 → 298ef41899acefa056eccf2345378078832db6d868d2377f471f896d5c1599f9} +6 -14
- package/dist/phoenix-ui/bundle/blobs/{1b7174a6f3f49a8efc8f7fa74ff84cf8960e2646b9bc976e4129a7c8ad608faa → 2b5cef2a14a5b1256ec6de9172a4f57f227948ced1921ef86e899f48524d7fb8} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{f148f630f43f3c91cf10feeda5a3c21448ca0683ce82d0fe763c1b43dee584d6 → 2d376cbbf7ec1e262614aac4f1b200e8f60e86e940c7137a2a26788bc857e509} +6 -7
- package/dist/phoenix-ui/bundle/blobs/2e5985a6eb339d70cfa25f6d2b420a0ef286caa8467f0226eb841702ec438d0b +46 -0
- package/dist/phoenix-ui/bundle/blobs/{7e199833376de5d2b6e088ee2965b448f94da842f28ccbd707e1cc3a295147c8 → 3322d691bf7b578b38723576df6b266993b2afb9e567b0cd1ded898ffe1dc17e} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{f82bfc4c6bc9169d4978d5e16fb925f6e152fdd21dc6a2fae719dfdc1645581f → 3625c5c2750f117021232ce81c1e39ac5ef7c21445729327b88cf54dfc937df0} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{2ceacad745665f79fd7ad5565646426a36a1b508c0469b931d0f202fad45a80b → 36310febe3e7bd4c9b21407603f2a04afefdc87b7821758bb47098b1b2876765} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{24d92a176573768bfffaf8f9c6483e58bceb5bf2a095d1cdf3d3a2c0bcda0707 → 3a9c2daf4ef284ca83b0e7f6fef3a5300e273c32b20769e090463cb8a8b0b06f} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{153e377c44b162cf0074698a61716db7f664db78f5635efa926bed484de4f54c → 3ce1a44efe39b93e7a39307c6a5e5f514e938fbae818e358986b4db1b05883ca} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{ad8ad0b7acf6c88ef9ce0ee1a640c2200da77821f0d97d605d4f1d4a10e5897a → 403fcde498d75abec7844ee31434201152ac229f7675772164d69563f0572da4} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{e8ee6c08e94b80324173d89d6f4cdc5b4829ea6858b3a11fc186588a490cada4 → 45d0e7eff0923c40296e971318756617944c4f1c97f03126cd74c0f8c629f8a4} +3 -1
- package/dist/phoenix-ui/bundle/blobs/{e23dabf908842be08751fc4e7f3b586e8c6b1ca0fc30303ec5317c77f83dcf26 → 4c0f396bfa85171f61972d3f3765733318769bb53fe06d2cdcb5d7f64578b5b8} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{a5517d32c1539c388c47c00879a0d807ed2989647bfa8c0844ef22328f75f5be → 4f6563876e0e0cc51c38a06767b8800eb3238611e9fdd11e5ff9dd487c742e74} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{a9d2203acd45ca33a13d5c68b02b23ed512425ac15f0e8438318ed124b43729d → 4fcada229d0c689c1a493d13184145a81ff40c498e882142ae390bda8e5b2492} +15 -0
- package/dist/phoenix-ui/bundle/blobs/5293adc55cf66068b6df20468ad9e91433fd1c48e75d66c86f874aca951a8fb9 +46 -0
- package/dist/phoenix-ui/bundle/blobs/{96e2e6bd4fcf9a2c6da65fb029f96d9176308fae2efd9d8653d3b8d838960e07 → 5436f0301ec258f3badcc6842019ee29a9b870d2727dc425fbc9cc0f4f88a284} +3 -1
- package/dist/phoenix-ui/bundle/blobs/{d0f4939e86a62b0ca437d51a997ceba7e77d64566b7fa8ca2747487e31ca819d → 54a09a4afdffca3cc135e5007716ea684dc1ed7a78349de859225c55a2fed810} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{d5102701d41f7c6195d410a873f742ac72c428b04196e47df5555c5ccaf80504 → 55277fa15eb4bd2f09bf7bb24c4e79df417b06654b1e10f502c7928b192fd7cc} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{878064e46af4e4109bab405f965e4149bee40bbe8912ffede10b74cec6cff027 → 567c3f42394f9aee9b048a001016a7ba9d8773d78027f8c995c379886634e719} +6 -3
- package/dist/phoenix-ui/bundle/blobs/57c8f33a7401f918566b8a4883871a69ef2223438c51f158c67f69e3250be465 +46 -0
- package/dist/phoenix-ui/bundle/blobs/{bc29e115fd337dda2cc7fc70b868dc18e426b08ec186fd270fcfe96bb2efaa5d → 61450dbef64859e3136cd17ae3e997134d5df5721d96985c4e07367f38522129} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{e3c1e925be0b2deb0e3765b83ab94b86560292364f275e98bb23a8bdf2c2e049 → 656bbfda3e72b40d53b5832293dc8700ad8a784e804a6246271bf0ea23527822} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{ffcaa5c58e779ec74d18bfc3013ad8003f6e7a23f629c1a3b878618961a8b6a3 → 68b454a99ba72c3b9b93ed96c4267847297169f6a5bf321b20e60d637c271068} +6 -3
- package/dist/phoenix-ui/bundle/blobs/68e44832c82b873b0aeb2da56def7b1de5f612c019445e5e28238c690124cbbe +46 -0
- package/dist/phoenix-ui/bundle/blobs/{45bbd4a8e02ca0012216c5c68e1dcd655ae0046bd50ba4295ef1f58ec4a0957c → 6b8d4c71355429a0b16bf367163fea46103a0a99b4e34f87608a39d85b1b980a} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{264815d0080e4862715a4f82542e66241c6563f07259d32aed4a17bbc329b9cc → 6cef874722442de7c60ea96f4221dc54cc4250a270cd05a1e818b58c5b43e232} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{2028a857c0476324a4256faf66693e2a869275e53a88d2b182a28b62c2a17893 → 7181f1f4c452c4345695f9100f6deb610f54f4c55f126043433a65b8491fc680} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{ccb76aabba47938d518a5d46a09d7e70c4a9222bc391e163763f2a7f12d30437 → 75850a8c9d2a8259f71ef1f86dd5d1325415227c1887e81a3467ae1ee400979f} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{4b03cdb5b17d534b5c7e84b079c066f59e8b60b9e454671a453867c4aeb9920d → 77bcc6c2d6ac00a8bfead3b6c6b5fc69f4b60bf086ebbc89271e1b23003168c9} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{9520e25a381c82af6f46e175b7c347d1dc513d933dd6cf8c52570c72abb4ca7f → 792552563745d208594cf4ade19110f2c811a0e1a2661ae1ef43a185ccff5a69} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{f7c8af35acf71a96056afb6afcff6a3ea42f65561f02652d04e11ad9ece9b8e9 → 7acd9b6f2f61b509c6a518508b8b52c2f0de67cdea4fd3f3d383b3ce8b38cf5e} +6 -7
- package/dist/phoenix-ui/bundle/blobs/7bacc1e3da7e8b7127ca3f8f69a2422098b9b2d23fbedefc56dfe76117c1c0e3 +46 -0
- package/dist/phoenix-ui/bundle/blobs/{78662176c1d31cec2d640f70df203f93cc1aec604cfc31619f4d2c62ab78861b → 8078b804bea630ed9c4ea07ec2f8ac92aba4128725860a66599eda90390d98f6} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{2291169748fe0c4c80634dff3083e7464f82d4358b9b2186759b316e612d466f → 88b150cb804bc141b80a5687a091f61b99627155c465a27adb88546ef615af92} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{bde88bf5ac59d744440d9b65f95ceaae01c803b101d48e6afd644a06eeb5de63 → 90f8b07ec12b78e633e306322cda58415aea70d8e0f9ff85bd439361c01dabe1} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{486145071d3445e82774f1b39b2bb15986ce52277e798a31ef7e9c27afb42af3 → 95b2944686e8258a655c3b5e6db68593e24e5199354e46aac553918ea2c0f55a} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{a1998e37084c11f496d4970f86a85d5a6375bbeaa4797b9f42bbac2d44048d23 → 979639f0e8859d600a8db57e733566d9ad37ff8258c3e05a6f9c0f3008e92373} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{3173293607055ea6e1a153ec6f51fb8154782157c8a61abf411da1f41be61728 → 9eaaa00049844060f842fe7fb73cc8edc4be5225cf38a96f33bda3ffc381456c} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{69086f7cb235b35ccc230e1f2da41cbb10cea8f0636b6d4514672113a3adc2cb → a420bbda622399399a6b5c7c3614dcaebc6c62faceaf7b3c83c97db53d26b165} +1 -1
- package/dist/phoenix-ui/bundle/blobs/a63054b8920905045753facece83f154bf806158c6f46ef380036e7a82d4fde5 +46 -0
- package/dist/phoenix-ui/bundle/blobs/{e802e4f7bdc89050a9c0f2ca506e1d493a2316e6e810c0336c57aa073717ef3e → a63788c764b0e4fdcbc029d46093016310edcbcf702d62688e5aaac7ddeee6fd} +3 -1
- package/dist/phoenix-ui/bundle/blobs/{827a211ea03d2cb44b4ea1bc81ba1a6dc51a87fa16d6692997c4e4751241c815 → acfc4c26b135d5b27fc0930738486c9d43d379704102fab45564320180942e4b} +4 -2
- package/dist/phoenix-ui/bundle/blobs/{8da14f3eabac53c990a66e721387367b46cb676cc4204e8c67d6f88c95a6217f → ad243ca615349127aa422bccc1a6604ad17149735659087ca5bab3c5fae97f4e} +6 -7
- package/dist/phoenix-ui/bundle/blobs/b63931066e2b2623d012e1a3c1a0769cc43ed6640460c3899669b45539d6a20d +46 -0
- package/dist/phoenix-ui/bundle/blobs/{bb65af58ecc3e0c0093dc9a705a7ab643035e4e592c3adb93583c82d1aef1f51 → bde793ab5ad776f7e6f6d25ce2b838c1b5022054a1dad5502a873423eb0ad917} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{8de7c97949b0bc4194b3be7645e4b13e9419767927482d01cf19476f6cbeda14 → c0a70ab2889e5c16e1f09249aefad948ce352c42cfe2b5afb758a4524e1b21b6} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{60b2914df394800688c4e20f4eb34e117cd430df9954cf4d48574734507723f4 → c9ee9006ae6c0194a5d39a9f0cb275c52d74a71ea3f6dc2b4c1c7bd89f48a2c0} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{6d8315e307eab11d3236935daf7267746cce6da3b51296e6999651ac97bda3a3 → cadc2daefcf238aab981fa56388381d3d5ab9c0686bb9dd8187035c9933dfcee} +6 -3
- package/dist/phoenix-ui/bundle/blobs/{d7b744fcc95164d96d9f811a93e2e8a374887146eb762ef775f3d3c7f27abc8d → cbea5728b90f58ff047be6ec4f14eed9d63c7148f5db99b5f147d2f17b472317} +1 -1
- package/dist/phoenix-ui/bundle/blobs/d408a4fea71b656aa8a5f6b5c037815a6e4229df336629ebddf4b5c85ed82fac +46 -0
- package/dist/phoenix-ui/bundle/blobs/{6ff950a5729ad9ea480ddf8c1b489b1c84189bbf7d6034b3b5a98e7c11a28cec → d9ae0c782d8a77787561536c0d7bc3b61175d59303f3cb8c20796f85140c6984} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{1d53e2226465b1047faec7cda2e743e61540ea69e073a3a4057e8d86b277405f → e2932d63eb7f52a82960a19ba79bc800ef4f212164e098f96dee7a4326302f38} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{fa576f6655b235bdea535198647305b7a3855e810180cbdf5d4c246c03c7d190 → e381bcfec42ac541d574011460600cf0818aa0e3362e8c6d205b4157890bf821} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{8fbce5b4859e6011afa83b1c07c4436589cd7181f9cb997538f4c27f196d0d34 → e415657d8725d9b60ece42cc18581da177c42fc47cb11e501db722a5f524ecf7} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{546a8428a2d8628bb3496fadb8c36a6dd65a33464291f214ceb581828f37081b → e46d63e5f8a6ead1d44c8b6302a0c7247af8585b975ab5715518381777622ede} +6 -3
- package/dist/phoenix-ui/bundle/blobs/ee63b3f9b354d69443d05ea4a338af56546897335ace7533546e10be56ea2207 +46 -0
- package/dist/phoenix-ui/bundle/blobs/{c23617601e9c4d97b775c175a5ce1642c40459b157d0821609fe1d253b83ced8 → ef7d57633252f4604749447608850e299c277f7dd1a32776685df0724b1051c8} +6 -7
- package/dist/phoenix-ui/bundle/blobs/{0581c8428246f43c5374393878a29e38f2b1865df87e6f13f2309b8eae54e76f → f0652c6ce41e6949133360178f192ea4ee9f6129a018c7a8b6652870c2330392} +1 -1
- package/dist/phoenix-ui/bundle/blobs/{cfac9eaf96481a8874391e5cecc6149abea1f1f19622f77f1ebb9e1876720e91 → f06c9d797ca2ac8c6a49d3dcd2e99d1b4df8bc9637a49c7e80f3161f49d06b1b} +4 -2
- package/dist/phoenix-ui/bundle/blobs/{d50780cb0a345051ae3fb12c5f8249a5dac9324d6a1963f04d03aaf769ffc27c → f077545caeb3ecb9f51a3f703a09736ca924869f4b4345c1b1ffbd9bf927a287} +6 -3
- package/dist/phoenix-ui/bundle/blobs/f781c5874bcd79ebe1b3b012275b9af7eae6c4867e9a16e29bb3d3ed3dc1940b +46 -0
- package/dist/phoenix-ui/bundle/blobs/fa10c0c926997387c89b7c1070e25b1b2b53ee891741e3c450a89ec6c6dcaf7d +46 -0
- package/dist/phoenix-ui/bundle/blobs/fb1a36ab7116b0c6709b89720cdded29d80982114c2f87cad614e01e126270dd +46 -0
- package/dist/phoenix-ui/bundle/blobs/fbd3214fc12ba70d9c69a995742ba73dc5365823af63afdc93c0c8a8f0c484af +46 -0
- package/dist/phoenix-ui/bundle/blobs/{77682e9d63b484f94a699f2c0b393e4540797d311362c384a3d643aec11d6985 → fea19b8381ec10ff8ed1923a336332974bbb552227ed215e7ddaaf0ec0112c1d} +1 -1
- package/dist/phoenix-ui/bundle/manifest.json +126 -126
- package/docs/phoenix-diagram.md +173 -0
- package/docs/phoenix-ui.md +40 -12
- package/package.json +14 -10
- package/skills/agi-mode/SKILL.md +3 -1
- package/skills/agi-mode/assets/sdlc/event.md +4 -5
- package/skills/agi-mode/assets/sdlc/object-status.md +1 -1
- package/skills/agi-mode/assets/sdlc/protocol.md +4 -2
- package/skills/agi-mode/evals/scenarios.md +108 -3
- package/skills/agi-mode/playbooks/authoring-a-skill.md +13 -2
- package/skills/agi-mode/playbooks/eval.md +19 -3
- package/skills/agi-mode/playbooks/hillclimb.md +4 -2
- package/skills/agi-mode/playbooks/reflection.md +1 -1
- package/skills/agi-mode/playbooks/sdlc.md +10 -6
- package/skills/agi-mode/playbooks/what-next.md +6 -2
- package/skills/agi-mode/references/eval-protocol.md +13 -4
- package/skills/agi-mode/references/interface-design.md +1 -0
- package/skills/agi-mode/references/remote-evolution.md +178 -0
- package/skills/agi-mode/references/skill-evolution.md +2 -0
- package/skills/agi-mode/scripts/evolution/compare.py +569 -0
- package/skills/agi-mode/scripts/evolution/evolution.py +2886 -0
- package/skills/agi-mode/scripts/evolution/examples/public-smoke.json +128 -0
- package/skills/agi-mode/scripts/evolution/examples/quality-calibration.json +128 -0
- package/skills/agi-mode/scripts/evolution/examples/quality-rubric.json +133 -0
- package/skills/agi-mode/scripts/evolution/protocol.py +1586 -0
- package/skills/agi-mode/scripts/evolution/pyproject.toml +16 -0
- package/skills/agi-mode/scripts/evolution/quality.py +689 -0
- package/skills/agi-mode/scripts/evolution/remote.py +2303 -0
- package/skills/agi-mode/scripts/evolution/tests/support.py +201 -0
- package/skills/agi-mode/scripts/evolution/tests/test_apply.py +643 -0
- package/skills/agi-mode/scripts/evolution/tests/test_bounded_exploration.py +236 -0
- package/skills/agi-mode/scripts/evolution/tests/test_bundles.py +414 -0
- package/skills/agi-mode/scripts/evolution/tests/test_calibration.py +460 -0
- package/skills/agi-mode/scripts/evolution/tests/test_compare.py +377 -0
- package/skills/agi-mode/scripts/evolution/tests/test_protocol.py +357 -0
- package/skills/agi-mode/scripts/evolution/tests/test_quality.py +647 -0
- package/skills/agi-mode/scripts/evolution/tests/test_remote.py +857 -0
- package/skills/agi-mode/scripts/evolution/tests/test_search.py +1109 -0
- package/skills/agi-mode/scripts/evolution/tests/test_typesafe.py +624 -0
- package/skills/agi-mode/scripts/evolution/tests/test_verifier_context.py +132 -0
- package/skills/agi-mode/scripts/evolution/typesafe_worker.py +145 -0
- package/skills/agi-mode/scripts/evolution/uv.lock +2548 -0
- package/skills/apply-code-review/SKILL.md +1 -0
- package/skills/apply-code-review/agents/openai.yaml +2 -0
- package/skills/apply-doc-review/SKILL.md +1 -0
- package/skills/apply-doc-review/agents/openai.yaml +2 -0
- package/skills/apply-ui-review/SKILL.md +1 -0
- package/skills/apply-ui-review/agents/openai.yaml +2 -0
- package/skills/autoresearch/SKILL.md +1 -0
- package/skills/clean-artifacts/SKILL.md +52 -0
- package/skills/clean-artifacts/agents/openai.yaml +4 -0
- package/skills/clean-artifacts/evals/scenarios.md +13 -0
- package/skills/clean-artifacts/references/history-cleanup.md +41 -0
- package/skills/crd/SKILL.md +100 -65
- package/skills/crd/assets/cr-template.md +15 -0
- package/skills/crd-respond/SKILL.md +17 -5
- package/skills/crd-respond/agents/openai.yaml +2 -2
- package/skills/crd-respond/assets/response-template.md +7 -0
- package/skills/crd-respond/evals/scenarios.md +7 -0
- package/skills/create-verification-skill/SKILL.md +1 -0
- package/skills/debugging/SKILL.md +1 -0
- package/skills/design-md/SKILL.md +1 -0
- package/skills/design-reference/SKILL.md +6 -2
- package/skills/design-reference/evals/scenarios.md +4 -0
- package/skills/dev-plan/SKILL.md +11 -6
- package/skills/dev-plan/references/execution-workflow.md +1 -1
- package/skills/dev-plan/references/plan-skeleton.md +234 -69
- package/skills/dev-plan/scripts/check_plan.py +1 -1
- package/skills/documentation-writer/SKILL.md +19 -14
- package/skills/documentation-writer/references/architecture.md +6 -2
- package/skills/documentation-writer/references/user-manual.md +2 -2
- package/skills/elintp/SKILL.md +12 -6
- package/skills/experience-capture/SKILL.md +1 -0
- package/skills/gdpr-compliant/SKILL.md +1 -0
- package/skills/kpi-dashboard-design/SKILL.md +1 -0
- package/skills/landing-page-conversion-audit/SKILL.md +1 -0
- package/skills/mcp-release-qa/SKILL.md +2 -1
- package/skills/meeting-minutes/SKILL.md +1 -0
- package/skills/optimize-agents-md/SKILL.md +128 -0
- package/skills/optimize-agents-md/assets/coding-principles.md +118 -0
- package/skills/phoenix-diagram/ENGINE.json +39 -0
- package/skills/phoenix-diagram/SKILL.md +109 -0
- package/skills/phoenix-diagram/examples/legacy.agent-tool-call.workflow.json +94 -0
- package/skills/phoenix-diagram/examples/legacy.async-job-roundtrip.sequence.json +61 -0
- package/skills/phoenix-diagram/examples/legacy.deployment-release.lifecycle.json +49 -0
- package/skills/phoenix-diagram/examples/legacy.product-analytics.dataflow.json +76 -0
- package/skills/{archify/test/fixtures/v1-baseline/web-app.architecture.json → phoenix-diagram/examples/legacy.web-app.architecture.json} +16 -11
- package/skills/phoenix-diagram/examples/minimal.architecture.json +11 -0
- package/skills/phoenix-diagram/examples/minimal.class.json +23 -0
- package/skills/phoenix-diagram/examples/minimal.dataflow.json +11 -0
- package/skills/phoenix-diagram/examples/minimal.erd.json +25 -0
- package/skills/phoenix-diagram/examples/minimal.lifecycle.json +11 -0
- package/skills/phoenix-diagram/examples/minimal.sequence.json +13 -0
- package/skills/phoenix-diagram/examples/minimal.timeline.json +15 -0
- package/skills/phoenix-diagram/examples/minimal.tree.json +13 -0
- package/skills/phoenix-diagram/examples/minimal.waterfall.json +12 -0
- package/skills/phoenix-diagram/examples/minimal.workflow.json +12 -0
- package/skills/phoenix-diagram/references/architecture-compare.md +111 -0
- package/skills/phoenix-diagram/references/authoring-defaults.md +42 -0
- package/skills/phoenix-diagram/references/brand-marks.md +52 -0
- package/skills/phoenix-diagram/references/compatibility.md +22 -0
- package/skills/phoenix-diagram/references/delivery-contract.md +127 -0
- package/skills/phoenix-diagram/references/extending-types.md +14 -0
- package/skills/phoenix-diagram/references/migration.md +57 -0
- package/skills/phoenix-diagram/references/preview.md +45 -0
- package/skills/phoenix-diagram/references/repository-authoring.md +29 -0
- package/skills/phoenix-diagram/references/viewer-runtime.md +27 -0
- package/skills/phoenix-diagram/schemas/README.md +23 -0
- package/skills/phoenix-diagram/schemas/architecture.schema.json +329 -0
- package/skills/phoenix-diagram/schemas/class.schema.json +348 -0
- package/skills/phoenix-diagram/schemas/common.schema.json +358 -0
- package/skills/{archify/schemas/lifecycle.schema.json → phoenix-diagram/schemas/dataflow.schema.json} +63 -58
- package/skills/phoenix-diagram/schemas/erd.schema.json +384 -0
- package/skills/phoenix-diagram/schemas/lifecycle.schema.json +202 -0
- package/skills/phoenix-diagram/schemas/sequence.schema.json +254 -0
- package/skills/phoenix-diagram/schemas/timeline.schema.json +170 -0
- package/skills/phoenix-diagram/schemas/tree.schema.json +144 -0
- package/skills/phoenix-diagram/schemas/waterfall.schema.json +150 -0
- package/skills/phoenix-diagram/schemas/workflow.schema.json +460 -0
- package/skills/phoenix-diagram/scripts/phoenix-bootstrap.cjs +589 -0
- package/skills/phoenix-diagram/scripts/phoenix-diagram +6 -0
- package/skills/phoenix-diagram/scripts/phoenix-launcher.cjs +10 -0
- package/skills/phoenix-ui/GENERATED.json +255 -240
- package/skills/phoenix-ui/SKILL.md +7 -4
- package/skills/phoenix-ui/evals/scenarios.md +8 -1
- package/skills/phoenix-ui/reference/craft-floor.md +3 -1
- package/skills/phoenix-ui/reference/critique.md +6 -14
- package/skills/phoenix-ui/reference/init.md +1 -1
- package/skills/phoenix-ui/reference/live.md +1 -1
- package/skills/phoenix-ui/reference/operate.md +15 -0
- package/skills/phoenix-ui/scripts/ENGINE.json +17 -17
- package/skills/phoenix-ui/scripts/phoenix-bootstrap.cjs +0 -4
- package/skills/portal-app-exchange/SKILL.md +1 -0
- package/skills/portal-dev-setup/SKILL.md +71 -12
- package/skills/portal-dev-setup/scripts/onebox.sh +69 -6
- package/skills/portal-external-app/SKILL.md +55 -16
- package/skills/portal-external-app/references/config-placement.md +108 -2
- package/skills/portal-external-app/references/registration-and-onebox.md +56 -2
- package/skills/portal-logging/SKILL.md +1 -0
- package/skills/portal-micro-app/SKILL.md +2 -1
- package/skills/portal-micro-app/references/sdk-reference.md +3 -1
- package/skills/prd/SKILL.md +238 -159
- package/skills/prd/evals/scenarios.md +13 -0
- package/skills/prd/references/prd-skeleton.md +14 -4
- package/skills/react/SKILL.md +1 -0
- package/skills/resolve-blocked/SKILL.md +46 -19
- package/skills/resolve-blocked/agents/openai.yaml +2 -2
- package/skills/resolve-blocked/evals/scenarios.md +22 -0
- package/skills/resolve-blocked/references/decisions-contract.md +7 -4
- package/skills/review-code/SKILL.md +308 -46
- package/skills/review-code/agents/openai.yaml +2 -0
- package/skills/review-dev-plan/SKILL.md +88 -33
- package/skills/review-dev-plan/agents/openai.yaml +2 -0
- package/skills/review-dev-plan/references/report-contract.md +53 -19
- package/skills/review-dev-plan/references/review-lenses.md +2 -1
- package/skills/review-prd/SKILL.md +90 -34
- package/skills/review-prd/agents/openai.yaml +2 -0
- package/skills/review-prd/references/report-contract.md +54 -18
- package/skills/review-prd-dev-gaps/SKILL.md +1 -0
- package/skills/review-prd-dev-gaps/agents/openai.yaml +2 -0
- package/skills/review-ui/SKILL.md +369 -71
- package/skills/review-ui/agents/openai.yaml +2 -0
- package/skills/review-ui/evals/scenarios.md +2 -0
- package/skills/rust/SKILL.md +76 -84
- package/skills/rust/references/api-design.md +97 -0
- package/skills/rust/references/{chapter_10.md → async-tokio.md} +42 -18
- package/skills/rust/references/behavioral-patterns.md +49 -0
- package/skills/rust/references/bevy.md +95 -0
- package/skills/rust/references/{chapter_02.md → clippy-and-linting.md} +26 -15
- package/skills/rust/references/{chapter_01.md → coding-idioms.md} +58 -7
- package/skills/rust/references/crates-and-dependencies.md +66 -0
- package/skills/rust/references/{chapter_08.md → documentation.md} +30 -3
- package/skills/rust/references/{chapter_04.md → error-handling.md} +32 -6
- package/skills/rust/references/{chapter_06.md → generics-and-dispatch.md} +28 -11
- package/skills/rust/references/macros.md +45 -0
- package/skills/rust/references/{chapter_03.md → performance.md} +25 -2
- package/skills/rust/references/{chapter_09.md → pointers-and-ownership.md} +37 -17
- package/skills/rust/references/{chapter_05.md → testing.md} +18 -2
- package/skills/rust/references/{chapter_07.md → type-state.md} +5 -1
- package/skills/rust/references/unsafe-and-ffi.md +56 -0
- package/skills/rust-port/SKILL.md +144 -0
- package/skills/rust-review/SKILL.md +177 -0
- package/skills/security-audit/SKILL.md +1 -0
- package/skills/spec-human-decision/SKILL.md +1 -0
- package/skills/sql-optimization/SKILL.md +1 -0
- package/skills/story-points/SKILL.md +1 -0
- package/skills/swarm/SKILL.md +1 -0
- package/skills/test-plan/SKILL.md +1 -0
- package/skills/ui-loop/SKILL.md +39 -0
- package/skills/ui-loop/agents/openai.yaml +3 -0
- package/skills/ui-pattern-research/SKILL.md +1 -0
- package/skills/use-llm-gateway/SKILL.md +58 -0
- package/skills/use-llm-gateway/agents/openai.yaml +4 -0
- package/skills/use-llm-gateway/references/api.md +129 -0
- package/skills/use-llm-gateway/references/recovery.md +86 -0
- package/skills/use-prompt-dsl/SKILL.md +165 -0
- package/skills/use-prompt-dsl/references/examples.md +726 -0
- package/skills/use-prompt-dsl/references/language.md +938 -0
- package/skills/use-rustify-docs/SKILL.md +60 -0
- package/skills/use-rustify-docs/agents/openai.yaml +4 -0
- package/skills/use-rustify-docs/references/rustify-docs.md +138 -0
- package/skills/use-task-gateway/SKILL.md +60 -0
- package/skills/use-task-gateway/agents/openai.yaml +3 -0
- package/skills/use-task-gateway/references/authentication.md +75 -0
- package/skills/use-task-gateway/references/task-api.md +121 -0
- package/skills/use-task-gateway/references/workers.md +85 -0
- package/skills/use-task-gateway/references/workflow-actions.md +66 -0
- package/skills/use-workflow-service/SKILL.md +57 -0
- package/skills/use-workflow-service/agents/openai.yaml +3 -0
- package/skills/use-workflow-service/references/action-provider.md +84 -0
- package/skills/use-workflow-service/references/authentication.md +76 -0
- package/skills/use-workflow-service/references/authoring.md +62 -0
- package/skills/use-workflow-service/references/mcp.md +47 -0
- package/skills/use-workflow-service/references/open-api.md +88 -0
- package/skills/use-workflow-service/references/orchestration-choice.md +63 -0
- package/skills/working-with-mbx/SKILL.md +1 -0
- package/skills/working-with-mise/SKILL.md +1 -0
- package/skills/xgent-app-release/SKILL.md +114 -16
- package/skills/xgent-app-release/references/build-and-package.md +2 -1
- package/skills/xgent-app-release/references/publish-api.md +6 -1
- package/skills/xgent-app-release/references/troubleshooting.md +2 -1
- package/skills/xgent-app-release/references/verify-preflight.mjs +21 -1
- package/skills/xgent-app-release/scripts/preflight.mjs +42 -3
- package/skills/xgent-image-push/SKILL.md +1 -0
- package/skills/xgent-init/SKILL.md +1 -0
- package/docs/claude-context-goal-guard.md +0 -124
- package/docs/codex-context-goal-guard.md +0 -109
- package/hooks/claude/context-goal-guard.js +0 -139
- package/hooks/claude/settings.example.json +0 -26
- package/hooks/codex/context-goal-guard.py +0 -189
- package/hooks/codex/hooks.example.json +0 -26
- package/skills/archify/SKILL.md +0 -137
- package/skills/archify/assets/template.html +0 -14937
- package/skills/archify/bin/archify.mjs +0 -2139
- package/skills/archify/bin/open-artifact.mjs +0 -86
- package/skills/archify/bin/preview.mjs +0 -653
- package/skills/archify/bin/visual-check.mjs +0 -829
- package/skills/archify/brand-marks/README.md +0 -31
- package/skills/archify/brand-marks/catalog.json +0 -131
- package/skills/archify/delta/architecture-delta.mjs +0 -1232
- package/skills/archify/examples/agent-run.lifecycle.json +0 -60
- package/skills/archify/examples/brand-aware-delivery.architecture.json +0 -47
- package/skills/archify/examples/cache-miss-request.sequence.json +0 -82
- package/skills/archify/examples/checkout-platform.base.architecture.json +0 -31
- package/skills/archify/examples/checkout-platform.head.architecture.json +0 -31
- package/skills/archify/examples/dataflow-product-analytics.html +0 -15048
- package/skills/archify/examples/event-stream.dataflow.json +0 -57
- package/skills/archify/examples/incident-response.workflow.json +0 -64
- package/skills/archify/examples/lifecycle-agent-run.html +0 -14983
- package/skills/archify/examples/production-deployment.architecture.json +0 -71
- package/skills/archify/examples/release-delivery.workflow.json +0 -62
- package/skills/archify/examples/sequence-cache-miss-request.html +0 -15063
- package/skills/archify/examples/web-app-rendered.html +0 -15012
- package/skills/archify/examples/workflow-agent-tool-call-rendered.html +0 -15054
- package/skills/archify/migrations/workflow-v2.mjs +0 -279
- package/skills/archify/package-lock.json +0 -149
- package/skills/archify/package.json +0 -39
- package/skills/archify/recipes/scenarios.mjs +0 -391
- package/skills/archify/references/authoring-contract.md +0 -247
- package/skills/archify/references/brand-marks.md +0 -65
- package/skills/archify/references/delivery-contract.md +0 -138
- package/skills/archify/references/viewer-runtime.md +0 -45
- package/skills/archify/renderers/architecture/grid.mjs +0 -62
- package/skills/archify/renderers/architecture/render-architecture.mjs +0 -1078
- package/skills/archify/renderers/dataflow/README.md +0 -104
- package/skills/archify/renderers/dataflow/render-dataflow.mjs +0 -483
- package/skills/archify/renderers/lifecycle/README.md +0 -115
- package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +0 -561
- package/skills/archify/renderers/sequence/README.md +0 -114
- package/skills/archify/renderers/sequence/render-sequence.mjs +0 -464
- package/skills/archify/renderers/shared/brand-marks.mjs +0 -563
- package/skills/archify/renderers/shared/cli.mjs +0 -218
- package/skills/archify/renderers/shared/desktop-readability.mjs +0 -26
- package/skills/archify/renderers/shared/diagnostics.mjs +0 -160
- package/skills/archify/renderers/shared/engineering-profiles.mjs +0 -157
- package/skills/archify/renderers/shared/generated-brand-marks.mjs +0 -2003
- package/skills/archify/renderers/shared/generated-validators.mjs +0 -13
- package/skills/archify/renderers/shared/geometry.mjs +0 -1423
- package/skills/archify/renderers/shared/i18n.mjs +0 -595
- package/skills/archify/renderers/shared/layout-report.mjs +0 -40
- package/skills/archify/renderers/shared/legend.mjs +0 -217
- package/skills/archify/renderers/shared/output-path.mjs +0 -340
- package/skills/archify/renderers/shared/repository-evidence.mjs +0 -239
- package/skills/archify/renderers/shared/repository-location.mjs +0 -58
- package/skills/archify/renderers/shared/text-fit.mjs +0 -49
- package/skills/archify/renderers/shared/utils.mjs +0 -232
- package/skills/archify/renderers/shared/validator.mjs +0 -86
- package/skills/archify/renderers/workflow/README.md +0 -223
- package/skills/archify/renderers/workflow/render-workflow.mjs +0 -35
- package/skills/archify/renderers/workflow/workflow-compiler.mjs +0 -4400
- package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +0 -144
- package/skills/archify/schemas/README.md +0 -211
- package/skills/archify/schemas/architecture.schema.json +0 -178
- package/skills/archify/schemas/common.schema.json +0 -115
- package/skills/archify/scripts/check-render-output.mjs +0 -916
- package/skills/archify/scripts/check-update.mjs +0 -1667
- package/skills/archify/scripts/generate-brand-marks.mjs +0 -141
- package/skills/archify/scripts/generate-validators.mjs +0 -66
- package/skills/archify/scripts/render-examples.mjs +0 -26
- package/skills/archify/scripts/update-contract.mjs +0 -182
- package/skills/archify/skill-release.json +0 -10
- package/skills/archify/test/adaptive-reader-layout.test.mjs +0 -86
- package/skills/archify/test/animation.test.mjs +0 -179
- package/skills/archify/test/architecture-compare-recovery.test.mjs +0 -157
- package/skills/archify/test/architecture-delta-markers.test.mjs +0 -137
- package/skills/archify/test/architecture-delta.test.mjs +0 -705
- package/skills/archify/test/artifact-receipt-flush.test.mjs +0 -126
- package/skills/archify/test/authored-reachability.test.mjs +0 -95
- package/skills/archify/test/authoring-safety-contract.test.mjs +0 -54
- package/skills/archify/test/automatic-port-spread.test.mjs +0 -376
- package/skills/archify/test/base-input-compatibility.test.mjs +0 -42
- package/skills/archify/test/brand-marks.test.mjs +0 -678
- package/skills/archify/test/chapter-delta-preview.test.mjs +0 -157
- package/skills/archify/test/chapter-handoff.test.mjs +0 -97
- package/skills/archify/test/chapter-rail.test.mjs +0 -86
- package/skills/archify/test/checkout-line-endings.test.mjs +0 -72
- package/skills/archify/test/ci-scope.test.mjs +0 -59
- package/skills/archify/test/clean-skill-staging.test.mjs +0 -440
- package/skills/archify/test/cli-output-types.test.mjs +0 -201
- package/skills/archify/test/cli.test.mjs +0 -986
- package/skills/archify/test/community-proof-intake.test.mjs +0 -125
- package/skills/archify/test/cursor-onboarding.test.mjs +0 -74
- package/skills/archify/test/degraded.test.mjs +0 -161
- package/skills/archify/test/delivery-contract.test.mjs +0 -52
- package/skills/archify/test/desktop-reader-browser.test.mjs +0 -80
- package/skills/archify/test/diagram-guide.test.mjs +0 -92
- package/skills/archify/test/engineering-profile.test.mjs +0 -209
- package/skills/archify/test/export-browser.test.mjs +0 -305
- package/skills/archify/test/export-cleanup-browser.test.mjs +0 -294
- package/skills/archify/test/finder-browser.test.mjs +0 -274
- package/skills/archify/test/finder.test.mjs +0 -104
- package/skills/archify/test/fixtures/automatic-routing-node-border-clearance.workflow.json +0 -40
- package/skills/archify/test/fixtures/fail-migration-cleanup.mjs +0 -17
- package/skills/archify/test/fixtures/issue-126/custom-widths.workflow.json +0 -18
- package/skills/archify/test/fixtures/issue-126/explicit-route.workflow.json +0 -28
- package/skills/archify/test/fixtures/issue-126/explicit-viewbox.workflow.json +0 -19
- package/skills/archify/test/fixtures/issue-126/labels.workflow.json +0 -18
- package/skills/archify/test/fixtures/v1-baseline/agent-run.lifecycle.json +0 -64
- package/skills/archify/test/fixtures/v1-baseline/agent-tool-call.workflow.json +0 -88
- package/skills/archify/test/fixtures/v1-baseline/cache-miss-request.sequence.json +0 -75
- package/skills/archify/test/fixtures/v1-baseline/event-stream.dataflow.json +0 -57
- package/skills/archify/test/fixtures/v1-baseline/product-analytics.dataflow.json +0 -70
- package/skills/archify/test/fixtures/v1-baseline/production-deployment.architecture.json +0 -71
- package/skills/archify/test/fixtures/v1-workflow-700x400.workflow.json +0 -22
- package/skills/archify/test/fixtures/v1-workflow-explicit-coordinates.workflow.json +0 -47
- package/skills/archify/test/focus-browser.test.mjs +0 -226
- package/skills/archify/test/gallery.test.mjs +0 -130
- package/skills/archify/test/generate-validators.test.mjs +0 -59
- package/skills/archify/test/generate-viewer.test.mjs +0 -165
- package/skills/archify/test/generated-artifact-xml.test.mjs +0 -88
- package/skills/archify/test/geometry.test.mjs +0 -739
- package/skills/archify/test/golden.mjs +0 -207
- package/skills/archify/test/grid.test.mjs +0 -47
- package/skills/archify/test/guide-page.test.mjs +0 -85
- package/skills/archify/test/guide.test.mjs +0 -81
- package/skills/archify/test/guided-views-browser.test.mjs +0 -460
- package/skills/archify/test/guided-views.test.mjs +0 -144
- package/skills/archify/test/helpers/desktop-browser.mjs +0 -48
- package/skills/archify/test/helpers/offline-fonts.mjs +0 -76
- package/skills/archify/test/helpers/viewer-click.mjs +0 -72
- package/skills/archify/test/helpers/xml.mjs +0 -37
- package/skills/archify/test/i18n.test.mjs +0 -422
- package/skills/archify/test/intent-trace-browser.test.mjs +0 -284
- package/skills/archify/test/intent-trace.test.mjs +0 -111
- package/skills/archify/test/landing.test.mjs +0 -97
- package/skills/archify/test/layout-rules.test.mjs +0 -1498
- package/skills/archify/test/legend-contract.test.mjs +0 -404
- package/skills/archify/test/motion-governor-browser.test.mjs +0 -342
- package/skills/archify/test/motion-governor.test.mjs +0 -111
- package/skills/archify/test/offline-font-browser.test.mjs +0 -210
- package/skills/archify/test/offline-self-containment.test.mjs +0 -80
- package/skills/archify/test/open-artifact.test.mjs +0 -118
- package/skills/archify/test/ordinary-model-floor.test.mjs +0 -1052
- package/skills/archify/test/output-path.test.mjs +0 -587
- package/skills/archify/test/presentation.test.mjs +0 -71
- package/skills/archify/test/preset-tryon.test.mjs +0 -113
- package/skills/archify/test/preview-contract.test.mjs +0 -40
- package/skills/archify/test/preview.test.mjs +0 -530
- package/skills/archify/test/proof-aperture.test.mjs +0 -78
- package/skills/archify/test/reach-share-card.test.mjs +0 -149
- package/skills/archify/test/reader-layout-browser.test.mjs +0 -320
- package/skills/archify/test/readme-showcase.test.mjs +0 -319
- package/skills/archify/test/real-repository-proof.test.mjs +0 -209
- package/skills/archify/test/relationship-direct-explorer.test.mjs +0 -115
- package/skills/archify/test/relationship-lens.test.mjs +0 -128
- package/skills/archify/test/relationship-permalink.test.mjs +0 -130
- package/skills/archify/test/relationship-pulse.test.mjs +0 -122
- package/skills/archify/test/release-identity.test.mjs +0 -476
- package/skills/archify/test/release-package-gates.test.mjs +0 -750
- package/skills/archify/test/render-output-checks.test.mjs +0 -588
- package/skills/archify/test/renderer-diagnostic-boundary.test.mjs +0 -107
- package/skills/archify/test/repair-receipt.test.mjs +0 -181
- package/skills/archify/test/repository-evidence-replacement.test.mjs +0 -136
- package/skills/archify/test/repository-evidence.test.mjs +0 -605
- package/skills/archify/test/repository-language-metadata.test.mjs +0 -67
- package/skills/archify/test/route-journey.test.mjs +0 -125
- package/skills/archify/test/route-probe-browser.test.mjs +0 -363
- package/skills/archify/test/route-probe.test.mjs +0 -114
- package/skills/archify/test/route-share-card.test.mjs +0 -163
- package/skills/archify/test/semantic-camera.test.mjs +0 -88
- package/skills/archify/test/semantic-flow.test.mjs +0 -90
- package/skills/archify/test/semantic-legend-gateway.test.mjs +0 -156
- package/skills/archify/test/semantic-lens-browser.test.mjs +0 -372
- package/skills/archify/test/semantic-lens.test.mjs +0 -103
- package/skills/archify/test/semantic-passport.test.mjs +0 -99
- package/skills/archify/test/semantic-radar.test.mjs +0 -735
- package/skills/archify/test/semantic-zoom.test.mjs +0 -86
- package/skills/archify/test/sequence-column-fit.test.mjs +0 -137
- package/skills/archify/test/settled-flow.test.mjs +0 -97
- package/skills/archify/test/share-card-export.test.mjs +0 -132
- package/skills/archify/test/site-language-continuity.test.mjs +0 -569
- package/skills/archify/test/site-language-integration.mjs +0 -9
- package/skills/archify/test/skill-metadata.test.mjs +0 -84
- package/skills/archify/test/stable-update-manifest.test.mjs +0 -167
- package/skills/archify/test/start-page.test.mjs +0 -312
- package/skills/archify/test/story-beat-navigator.test.mjs +0 -103
- package/skills/archify/test/story-carrier.test.mjs +0 -90
- package/skills/archify/test/story-director-strip.test.mjs +0 -91
- package/skills/archify/test/story-follow-camera.test.mjs +0 -103
- package/skills/archify/test/story-horizon.test.mjs +0 -91
- package/skills/archify/test/story-moment-link.test.mjs +0 -93
- package/skills/archify/test/story-shelf.test.mjs +0 -84
- package/skills/archify/test/story-trail.test.mjs +0 -81
- package/skills/archify/test/toolbar-polish.test.mjs +0 -57
- package/skills/archify/test/update-contract.test.mjs +0 -133
- package/skills/archify/test/update-notifier.test.mjs +0 -3317
- package/skills/archify/test/v1-compatibility.test.mjs +0 -329
- package/skills/archify/test/vertical-edge.test.mjs +0 -68
- package/skills/archify/test/viewer-camera-browser.test.mjs +0 -395
- package/skills/archify/test/viewer-chrome-layout.test.mjs +0 -1104
- package/skills/archify/test/viewer-identifiers-browser.test.mjs +0 -135
- package/skills/archify/test/visual-check.test.mjs +0 -400
- package/skills/archify/test/webm-artifact.smoke.mjs +0 -1855
- package/skills/archify/test/workflow-compiler-hard-contract.test.mjs +0 -2136
- package/skills/archify/test/workflow-compiler.test.mjs +0 -1163
- package/skills/archify/test/workflow-migration.test.mjs +0 -620
- package/skills/archify/test/workflow-semantic-contract.test.mjs +0 -131
- /package/{skills/archify/THIRD_PARTY_NOTICES.md → dist/phoenix-diagram/THIRD-PARTY-LICENSES/0b7fb59365dc21c93b0f62526d74ee913aeec9ae532c112bc76afa7edcd56e14.txt} +0 -0
- /package/{skills/archify/LICENSE → dist/phoenix-diagram/THIRD-PARTY-LICENSES/b799ab081703e7821ae5096d2c1abdf14bbdc75e8ea1c4045998c36ed6db9706.txt} +0 -0
- /package/{skills/archify/assets/JetBrainsMono-OFL.txt → dist/phoenix-diagram/THIRD-PARTY-LICENSES/c1ab7c666206842a02b35b30770dac0d7a10156ed401c9defc3f02a754d89e90.txt} +0 -0
- /package/{skills/archify/examples/agent-tool-call.workflow.json → dist/phoenix-diagram/bundle/examples/legacy.agent-tool-call.workflow.json} +0 -0
- /package/{skills/archify/examples/async-job-roundtrip.sequence.json → dist/phoenix-diagram/bundle/examples/legacy.async-job-roundtrip.sequence.json} +0 -0
- /package/{skills/archify/examples/deployment-release.lifecycle.json → dist/phoenix-diagram/bundle/examples/legacy.deployment-release.lifecycle.json} +0 -0
- /package/{skills/archify/examples/product-analytics.dataflow.json → dist/phoenix-diagram/bundle/examples/legacy.product-analytics.dataflow.json} +0 -0
- /package/{skills/archify/examples/web-app.architecture.json → dist/phoenix-diagram/bundle/examples/legacy.web-app.architecture.json} +0 -0
|
@@ -0,0 +1,2303 @@
|
|
|
1
|
+
"""SSH/Docker execution and its standalone bounded command supervisor."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import signal
|
|
12
|
+
import shlex
|
|
13
|
+
import stat
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import tarfile
|
|
18
|
+
import time
|
|
19
|
+
import uuid
|
|
20
|
+
import urllib.request
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
TASK_PATH = "/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RemoteError(RuntimeError):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UnresolvedError(RemoteError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def encoded(value):
|
|
35
|
+
return json.dumps(
|
|
36
|
+
value,
|
|
37
|
+
ensure_ascii=False,
|
|
38
|
+
sort_keys=True,
|
|
39
|
+
separators=(",", ":"),
|
|
40
|
+
allow_nan=False,
|
|
41
|
+
).encode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def digest(content):
|
|
45
|
+
return hashlib.sha256(content).hexdigest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_json(path):
|
|
49
|
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_bytes(path, content):
|
|
53
|
+
path = Path(path)
|
|
54
|
+
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
55
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".receipt-", dir=path.parent)
|
|
56
|
+
try:
|
|
57
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
58
|
+
stream.write(content)
|
|
59
|
+
stream.flush()
|
|
60
|
+
os.fsync(stream.fileno())
|
|
61
|
+
os.replace(temporary, path)
|
|
62
|
+
finally:
|
|
63
|
+
if os.path.exists(temporary):
|
|
64
|
+
os.unlink(temporary)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def save_json(path, value):
|
|
68
|
+
save_bytes(path, encoded(value) + b"\n")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def ssh_request(runtime, source, operation, execution, payload):
|
|
72
|
+
runner_hash = digest(source)
|
|
73
|
+
if runner_hash != execution["runner_hash"]:
|
|
74
|
+
raise UnresolvedError("runner source differs from the frozen execution")
|
|
75
|
+
script = runtime["remote_root"] + "/tools/" + runner_hash + "/remote.py"
|
|
76
|
+
bootstrap = (
|
|
77
|
+
"import hashlib,pathlib,sys; p=pathlib.Path(sys.argv[1]); b=sys.stdin.buffer.read(); "
|
|
78
|
+
"assert hashlib.sha256(b).hexdigest()==sys.argv[2]; "
|
|
79
|
+
"p.parent.mkdir(parents=True,exist_ok=True,mode=0o700); "
|
|
80
|
+
"assert not p.is_symlink(); "
|
|
81
|
+
"assert not p.exists() or p.read_bytes()==b; "
|
|
82
|
+
"p.write_bytes(b) if not p.exists() else None"
|
|
83
|
+
)
|
|
84
|
+
ssh = [
|
|
85
|
+
"ssh",
|
|
86
|
+
"-o",
|
|
87
|
+
"BatchMode=yes",
|
|
88
|
+
"-o",
|
|
89
|
+
"ForwardAgent=no",
|
|
90
|
+
"-o",
|
|
91
|
+
"ClearAllForwardings=yes",
|
|
92
|
+
"-o",
|
|
93
|
+
"ConnectTimeout=10",
|
|
94
|
+
"-o",
|
|
95
|
+
"ConnectionAttempts=1",
|
|
96
|
+
"--",
|
|
97
|
+
runtime["ssh_target"],
|
|
98
|
+
]
|
|
99
|
+
try:
|
|
100
|
+
deployed = subprocess.run(
|
|
101
|
+
[*ssh, shlex.join(["python3", "-c", bootstrap, script, runner_hash])],
|
|
102
|
+
input=source,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
timeout=30,
|
|
105
|
+
)
|
|
106
|
+
if deployed.returncode:
|
|
107
|
+
raise RemoteError("remote runner could not be staged")
|
|
108
|
+
request = {
|
|
109
|
+
"runtime": runtime,
|
|
110
|
+
"operation": operation,
|
|
111
|
+
"execution": execution,
|
|
112
|
+
"payload": payload,
|
|
113
|
+
}
|
|
114
|
+
timeout = 300
|
|
115
|
+
if operation in {"create", "setup_exec"}:
|
|
116
|
+
timeout = max(1, min(1000, execution["command_deadline"] - time.time()))
|
|
117
|
+
response = subprocess.run(
|
|
118
|
+
[*ssh, shlex.join(["python3", script, "--dispatch"])],
|
|
119
|
+
input=encoded(request),
|
|
120
|
+
capture_output=True,
|
|
121
|
+
timeout=timeout,
|
|
122
|
+
)
|
|
123
|
+
result = json.loads(response.stdout)
|
|
124
|
+
if not isinstance(result, dict):
|
|
125
|
+
raise UnresolvedError("remote response has no execution object")
|
|
126
|
+
if "error" in result:
|
|
127
|
+
error = UnresolvedError if result.get("unresolved") else RemoteError
|
|
128
|
+
raise error(result["error"])
|
|
129
|
+
if response.returncode:
|
|
130
|
+
raise UnresolvedError("remote request did not return a confirmed result")
|
|
131
|
+
return result
|
|
132
|
+
except (OSError, subprocess.TimeoutExpired, json.JSONDecodeError) as error:
|
|
133
|
+
raise UnresolvedError("SSH response could not be confirmed") from error
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RemoteClient:
|
|
137
|
+
def __init__(self, runtime, runner_source=None):
|
|
138
|
+
self.runtime = runtime
|
|
139
|
+
self.runner_source = (
|
|
140
|
+
Path(__file__).read_bytes() if runner_source is None else runner_source
|
|
141
|
+
)
|
|
142
|
+
self.runner_hash = digest(self.runner_source)
|
|
143
|
+
|
|
144
|
+
def _rpc(self, operation, execution, payload=None):
|
|
145
|
+
return ssh_request(
|
|
146
|
+
self.runtime, self.runner_source, operation, execution, payload
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def submit(self, execution, payload, save):
|
|
150
|
+
if execution["submission_status"] != "not_submitted":
|
|
151
|
+
return self.query(execution, save)
|
|
152
|
+
execution["submission_status"] = "intent_saved"
|
|
153
|
+
save(execution)
|
|
154
|
+
try:
|
|
155
|
+
resource = self._rpc("create", execution, payload)
|
|
156
|
+
execution.update(
|
|
157
|
+
sandbox_id=resource["sandbox_id"],
|
|
158
|
+
engine_id=resource["engine_id"],
|
|
159
|
+
submission_status="resource_created",
|
|
160
|
+
)
|
|
161
|
+
save(execution)
|
|
162
|
+
if payload.get("needs_codex"):
|
|
163
|
+
self.prepare_codex(execution, payload)
|
|
164
|
+
if payload.get("needs_typesafe"):
|
|
165
|
+
self.prepare_typesafe(execution, payload)
|
|
166
|
+
if time.time() >= execution["command_deadline"]:
|
|
167
|
+
raise UnresolvedError(
|
|
168
|
+
"command deadline reached during setup; original command was not launched"
|
|
169
|
+
)
|
|
170
|
+
execution["submission_status"] = "launch_intent_saved"
|
|
171
|
+
save(execution)
|
|
172
|
+
self._rpc("launch", execution)
|
|
173
|
+
execution["submission_status"] = "runner_submitted"
|
|
174
|
+
save(execution)
|
|
175
|
+
return self.query(execution, save)
|
|
176
|
+
except (RemoteError, KeyError) as error:
|
|
177
|
+
execution["submission_status"] = "unresolved"
|
|
178
|
+
save(execution)
|
|
179
|
+
raise UnresolvedError(
|
|
180
|
+
"submission result is unresolved; query the registered execution"
|
|
181
|
+
) from error
|
|
182
|
+
|
|
183
|
+
def query(self, execution, save):
|
|
184
|
+
from protocol import ProtocolError, validate_execution
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
for attempt in range(3):
|
|
188
|
+
try:
|
|
189
|
+
result = self._rpc("query", execution)
|
|
190
|
+
break
|
|
191
|
+
except UnresolvedError as error:
|
|
192
|
+
if (
|
|
193
|
+
str(error) != "original command receipt is unavailable"
|
|
194
|
+
or attempt == 2
|
|
195
|
+
or time.time() >= execution["resource_deadline"]
|
|
196
|
+
):
|
|
197
|
+
raise
|
|
198
|
+
time.sleep(0.25)
|
|
199
|
+
receipt = result["receipt"]
|
|
200
|
+
validate_execution(receipt)
|
|
201
|
+
immutable = [
|
|
202
|
+
"schema_version",
|
|
203
|
+
"remote_task_id",
|
|
204
|
+
"role",
|
|
205
|
+
"idempotency_key",
|
|
206
|
+
"input_hash",
|
|
207
|
+
"command_hash",
|
|
208
|
+
"runner_hash",
|
|
209
|
+
"runner_version",
|
|
210
|
+
"ssh_target",
|
|
211
|
+
"remote_root",
|
|
212
|
+
"sandbox_name",
|
|
213
|
+
"receipt_path",
|
|
214
|
+
"command_deadline",
|
|
215
|
+
"resource_deadline",
|
|
216
|
+
]
|
|
217
|
+
if any(receipt[key] != execution[key] for key in immutable):
|
|
218
|
+
raise UnresolvedError("remote execution identity has drifted")
|
|
219
|
+
for key in ["sandbox_id", "engine_id"]:
|
|
220
|
+
if execution[key] is not None and receipt[key] != execution[key]:
|
|
221
|
+
raise UnresolvedError("remote resource identity has drifted")
|
|
222
|
+
for key in [
|
|
223
|
+
"sandbox_id",
|
|
224
|
+
"engine_id",
|
|
225
|
+
"phase",
|
|
226
|
+
"process",
|
|
227
|
+
"exit_code",
|
|
228
|
+
"termination_reason",
|
|
229
|
+
"artifact_manifest",
|
|
230
|
+
]:
|
|
231
|
+
execution[key] = receipt[key]
|
|
232
|
+
save(execution)
|
|
233
|
+
phase = receipt["phase"]
|
|
234
|
+
if (
|
|
235
|
+
phase in {"starting", "running", "capturing"}
|
|
236
|
+
and not result["supervisor_alive"]
|
|
237
|
+
):
|
|
238
|
+
if result["command_alive"]:
|
|
239
|
+
raise UnresolvedError(
|
|
240
|
+
"original supervisor is unavailable while the command is still running"
|
|
241
|
+
)
|
|
242
|
+
recovered = self._rpc("capture_only", execution)
|
|
243
|
+
if recovered.get("receipt", {}).get("phase") != "captured":
|
|
244
|
+
raise UnresolvedError(
|
|
245
|
+
"capture-only could not preserve the stopped execution"
|
|
246
|
+
)
|
|
247
|
+
return self.query(execution, save)
|
|
248
|
+
if phase == "prepared" and execution["submission_status"] in {
|
|
249
|
+
"launch_intent_saved",
|
|
250
|
+
"runner_submitted",
|
|
251
|
+
"unresolved",
|
|
252
|
+
}:
|
|
253
|
+
raise UnresolvedError("launch window is unresolved")
|
|
254
|
+
if phase == "captured" and receipt["exit_code"] is None:
|
|
255
|
+
raise UnresolvedError("original exit receipt is missing")
|
|
256
|
+
if phase in {"running", "capturing", "captured"}:
|
|
257
|
+
execution["submission_status"] = "runner_submitted"
|
|
258
|
+
save(execution)
|
|
259
|
+
return result
|
|
260
|
+
except (RemoteError, ProtocolError, KeyError) as error:
|
|
261
|
+
execution["submission_status"] = "unresolved"
|
|
262
|
+
save(execution)
|
|
263
|
+
if isinstance(error, UnresolvedError):
|
|
264
|
+
raise
|
|
265
|
+
raise UnresolvedError(
|
|
266
|
+
"registered remote execution could not be queried"
|
|
267
|
+
) from error
|
|
268
|
+
|
|
269
|
+
def cancel(self, execution, reason, save):
|
|
270
|
+
execution["cancel_reason"] = reason
|
|
271
|
+
save(execution)
|
|
272
|
+
return self._rpc("cancel", execution, {"reason": reason})
|
|
273
|
+
|
|
274
|
+
def export(self, execution, destination, save):
|
|
275
|
+
from protocol import validate_execution
|
|
276
|
+
|
|
277
|
+
validate_execution(execution)
|
|
278
|
+
if execution["phase"] != "captured":
|
|
279
|
+
raise UnresolvedError("execution has no complete capture receipt")
|
|
280
|
+
destination = Path(destination)
|
|
281
|
+
if destination.is_symlink():
|
|
282
|
+
raise UnresolvedError("artifact destination cannot be a symlink")
|
|
283
|
+
destination = destination.resolve()
|
|
284
|
+
destination.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
285
|
+
execution["export_status"] = "partial"
|
|
286
|
+
save(execution)
|
|
287
|
+
for row in execution["artifact_manifest"]:
|
|
288
|
+
path = destination / row["path"]
|
|
289
|
+
if any(parent.is_symlink() for parent in [path, *path.parents]):
|
|
290
|
+
raise UnresolvedError("artifact path contains a symlink")
|
|
291
|
+
if path.exists():
|
|
292
|
+
content = path.read_bytes()
|
|
293
|
+
else:
|
|
294
|
+
result = self._rpc("read_artifact", execution, {"path": row["path"]})
|
|
295
|
+
try:
|
|
296
|
+
content = base64.b64decode(result["data"], validate=True)
|
|
297
|
+
except (ValueError, KeyError) as error:
|
|
298
|
+
raise UnresolvedError("artifact download is incomplete") from error
|
|
299
|
+
if len(content) != row["size"] or digest(content) != row["sha256"]:
|
|
300
|
+
raise UnresolvedError(
|
|
301
|
+
"artifact bytes do not match the captured manifest"
|
|
302
|
+
)
|
|
303
|
+
if not path.exists():
|
|
304
|
+
save_bytes(path, content)
|
|
305
|
+
execution["export_status"] = "exported"
|
|
306
|
+
save(execution)
|
|
307
|
+
return destination
|
|
308
|
+
|
|
309
|
+
def cleanup(self, execution, save, force_reason=None):
|
|
310
|
+
if execution["export_status"] != "exported" and not force_reason:
|
|
311
|
+
raise UnresolvedError(
|
|
312
|
+
"cleanup requires exported evidence or a recorded force reason"
|
|
313
|
+
)
|
|
314
|
+
execution["cleanup_status"] = "pending"
|
|
315
|
+
save(execution)
|
|
316
|
+
result = self._rpc("cleanup", execution, {"force_reason": force_reason})
|
|
317
|
+
if not result.get("terminated"):
|
|
318
|
+
raise UnresolvedError("registered resource termination is not confirmed")
|
|
319
|
+
execution["cleanup_status"] = "terminated"
|
|
320
|
+
save(execution)
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
def prepare_codex(self, execution, payload):
|
|
324
|
+
import asyncio
|
|
325
|
+
from pathlib import PurePosixPath
|
|
326
|
+
from types import SimpleNamespace
|
|
327
|
+
from harbor.agents.installed.codex import Codex
|
|
328
|
+
|
|
329
|
+
client = self
|
|
330
|
+
|
|
331
|
+
class DockerSetup:
|
|
332
|
+
default_user = None
|
|
333
|
+
|
|
334
|
+
async def exec(
|
|
335
|
+
self, command, user=None, env=None, cwd=None, timeout_sec=None
|
|
336
|
+
):
|
|
337
|
+
result = client._rpc(
|
|
338
|
+
"setup_exec",
|
|
339
|
+
execution,
|
|
340
|
+
{
|
|
341
|
+
"command": command,
|
|
342
|
+
"user": user,
|
|
343
|
+
"cwd": cwd,
|
|
344
|
+
"env": {
|
|
345
|
+
"HOME": "/home/evolution",
|
|
346
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin",
|
|
347
|
+
**(env or {}),
|
|
348
|
+
},
|
|
349
|
+
"timeout_sec": timeout_sec,
|
|
350
|
+
},
|
|
351
|
+
)
|
|
352
|
+
return SimpleNamespace(**result)
|
|
353
|
+
|
|
354
|
+
async def upload_file(self, source_path, target_path):
|
|
355
|
+
client._rpc(
|
|
356
|
+
"setup_files",
|
|
357
|
+
execution,
|
|
358
|
+
{target_path: encoded_file(Path(source_path).read_bytes(), 0o600)},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
class IsolatedCodex(Codex):
|
|
362
|
+
_REMOTE_CODEX_HOME = PurePosixPath("/home/evolution/.codex")
|
|
363
|
+
|
|
364
|
+
async def install(self, environment):
|
|
365
|
+
cached = client._rpc("restore_codex", execution)
|
|
366
|
+
if not cached["restored"]:
|
|
367
|
+
available = await environment.exec(
|
|
368
|
+
command="command -v node && command -v npm", user="root"
|
|
369
|
+
)
|
|
370
|
+
architecture = (
|
|
371
|
+
"arm64"
|
|
372
|
+
if client.runtime["codex_architecture"] == "aarch64"
|
|
373
|
+
else "x64"
|
|
374
|
+
)
|
|
375
|
+
command = (
|
|
376
|
+
"npm install --global --force --prefix /opt/evolution-codex --registry https://registry.npmjs.org @openai/codex@"
|
|
377
|
+
+ self._version
|
|
378
|
+
+ "-linux-"
|
|
379
|
+
+ architecture
|
|
380
|
+
)
|
|
381
|
+
if available.return_code != 0:
|
|
382
|
+
command = shlex.join(
|
|
383
|
+
[
|
|
384
|
+
"python3",
|
|
385
|
+
"-c",
|
|
386
|
+
"import sys; sys.path.insert(0,'/evolution-control'); "
|
|
387
|
+
"from remote import install_codex_archive; "
|
|
388
|
+
"install_codex_archive(sys.argv[1],sys.argv[2],sys.argv[3])",
|
|
389
|
+
"/opt/evolution-codex/lib/node_modules/@openai/codex",
|
|
390
|
+
self._version,
|
|
391
|
+
architecture,
|
|
392
|
+
]
|
|
393
|
+
)
|
|
394
|
+
installed = await environment.exec(
|
|
395
|
+
command=command,
|
|
396
|
+
user="root",
|
|
397
|
+
timeout_sec=min(
|
|
398
|
+
900,
|
|
399
|
+
max(1, int(execution["command_deadline"] - time.time())),
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
if installed.return_code != 0:
|
|
403
|
+
raise RemoteError("fixed Codex package installation failed")
|
|
404
|
+
link = (
|
|
405
|
+
"import os,pathlib; root=pathlib.Path('/opt/evolution-codex/lib/node_modules/@openai/codex'); "
|
|
406
|
+
"names={'codex','codex-code-mode-host','codex-exec-server','rg','bwrap'}; "
|
|
407
|
+
"bins=[p for p in root.rglob('*') if p.is_file() and p.name in names and os.access(p,os.X_OK)]; "
|
|
408
|
+
"[(pathlib.Path('/usr/local/bin',p.name).unlink(missing_ok=True),pathlib.Path('/usr/local/bin',p.name).symlink_to(p)) for p in bins]"
|
|
409
|
+
)
|
|
410
|
+
await self.exec_as_root(
|
|
411
|
+
environment, command=shlex.join(["python3", "-c", link])
|
|
412
|
+
)
|
|
413
|
+
if not cached["restored"]:
|
|
414
|
+
client._rpc("cache_codex", execution)
|
|
415
|
+
|
|
416
|
+
async def install():
|
|
417
|
+
with tempfile.TemporaryDirectory(prefix="evolution-setup-") as logs:
|
|
418
|
+
agent = IsolatedCodex(
|
|
419
|
+
logs_dir=Path(logs),
|
|
420
|
+
model_name=self.runtime.get("model"),
|
|
421
|
+
version=self.runtime["codex_version"],
|
|
422
|
+
config=payload["codex_config"],
|
|
423
|
+
)
|
|
424
|
+
environment = DockerSetup()
|
|
425
|
+
await agent.install(environment)
|
|
426
|
+
await agent._upload_effective_config(
|
|
427
|
+
environment,
|
|
428
|
+
agent._build_effective_config(),
|
|
429
|
+
"/home/evolution/.codex/config.toml",
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
asyncio.run(install())
|
|
433
|
+
import toml
|
|
434
|
+
|
|
435
|
+
receipt = self._rpc("finalize_setup", execution)
|
|
436
|
+
if receipt["config_sha256"] != digest(
|
|
437
|
+
toml.dumps(payload["codex_config"]).encode()
|
|
438
|
+
):
|
|
439
|
+
raise RemoteError(
|
|
440
|
+
"native Codex configuration bytes differ from the declared configuration"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def prepare_typesafe(self, execution, payload):
|
|
444
|
+
key = os.environ.get("TYPESAFE_API_KEY")
|
|
445
|
+
if not key:
|
|
446
|
+
raise RemoteError("TypeSafe controller credential is unavailable")
|
|
447
|
+
result = self._rpc("prepare_typesafe", execution, {"key": key})
|
|
448
|
+
if result != {"prepared": True}:
|
|
449
|
+
raise UnresolvedError("TypeSafe credential preparation is unresolved")
|
|
450
|
+
|
|
451
|
+
def destroy_typesafe(self, execution):
|
|
452
|
+
result = self._rpc("destroy_typesafe", execution)
|
|
453
|
+
if result != {"destroyed": True}:
|
|
454
|
+
raise UnresolvedError("TypeSafe credential destruction is unresolved")
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def isolation_script(case, *, schema_version=1):
|
|
458
|
+
settings = {"repo": case["repo_path"], "base_commit": case["base_commit"]}
|
|
459
|
+
return (
|
|
460
|
+
"settings="
|
|
461
|
+
+ repr(settings)
|
|
462
|
+
+ "\n"
|
|
463
|
+
+ """
|
|
464
|
+
import json, os, pathlib, socket, subprocess
|
|
465
|
+
checks = {'unprivileged_user': os.getuid() == 10000}
|
|
466
|
+
"""
|
|
467
|
+
+ ("""import shutil
|
|
468
|
+
npm = shutil.which('npm')
|
|
469
|
+
checks['installed_npm_ready'] = True
|
|
470
|
+
if npm:
|
|
471
|
+
try:
|
|
472
|
+
started = subprocess.run([npm, '--version'], text=True, capture_output=True, timeout=30)
|
|
473
|
+
checks['installed_npm_ready'] = started.returncode == 0 and bool(started.stdout.strip())
|
|
474
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
475
|
+
checks['installed_npm_ready'] = False
|
|
476
|
+
""" if schema_version == 2 else "")
|
|
477
|
+
+ """for name, path in [('auth_hidden', '/home/evolution/.codex/auth.json'), ('control_hidden', '/evolution-control/receipt.json')]:
|
|
478
|
+
try:
|
|
479
|
+
pathlib.Path(path).read_bytes()
|
|
480
|
+
checks[name] = False
|
|
481
|
+
except (PermissionError, FileNotFoundError):
|
|
482
|
+
checks[name] = True
|
|
483
|
+
try:
|
|
484
|
+
pathlib.Path('/evolution-control/receipt.json').write_text('forbidden')
|
|
485
|
+
checks['control_write_denied'] = False
|
|
486
|
+
except (PermissionError, FileNotFoundError):
|
|
487
|
+
checks['control_write_denied'] = True
|
|
488
|
+
for name, path in [('no_grader', '/tests/test.sh'), ('no_oracle', '/solution/gold_patch.diff')]:
|
|
489
|
+
try:
|
|
490
|
+
checks[name] = not pathlib.Path(path).exists()
|
|
491
|
+
except PermissionError:
|
|
492
|
+
checks[name] = True
|
|
493
|
+
checks['clean_tool_environment'] = not any(any(word in key.upper() for word in ['TOKEN','SECRET','API_KEY','SSH_AUTH']) for key in os.environ)
|
|
494
|
+
checks['base_matches'] = subprocess.check_output(['git','-C',settings['repo'],'rev-parse','HEAD'],text=True).strip() == settings['base_commit']
|
|
495
|
+
ancestors = set(subprocess.check_output(['git','-C',settings['repo'],'rev-list',settings['base_commit']],text=True).splitlines())
|
|
496
|
+
objects = subprocess.check_output(['git','-C',settings['repo'],'cat-file','--batch-all-objects','--batch-check=%(objectname) %(objecttype)'],text=True).splitlines()
|
|
497
|
+
commits = {line.split()[0] for line in objects if line.endswith(' commit')}
|
|
498
|
+
checks['no_later_git_objects'] = commits <= ancestors
|
|
499
|
+
try:
|
|
500
|
+
connection = socket.create_connection(('github.com', 443), timeout=2)
|
|
501
|
+
connection.close()
|
|
502
|
+
checks['external_network_blocked'] = False
|
|
503
|
+
except OSError:
|
|
504
|
+
checks['external_network_blocked'] = True
|
|
505
|
+
try:
|
|
506
|
+
with socket.socket() as server:
|
|
507
|
+
server.bind(('127.0.0.1', 0))
|
|
508
|
+
server.listen(1)
|
|
509
|
+
connection = socket.create_connection(server.getsockname(), timeout=2)
|
|
510
|
+
connection.close()
|
|
511
|
+
checks['loopback_available'] = True
|
|
512
|
+
except OSError:
|
|
513
|
+
checks['loopback_available'] = False
|
|
514
|
+
marker = pathlib.Path(settings['repo']) / 'src/evolution_capture_probe.js'
|
|
515
|
+
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
516
|
+
marker.write_text('// deterministic capture probe\\n')
|
|
517
|
+
print(json.dumps({'isolation_passed': all(checks.values()), 'checks': checks, 'environment_names': sorted(os.environ)}))
|
|
518
|
+
raise SystemExit(0 if all(checks.values()) else 1)
|
|
519
|
+
"""
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def native_skill_files(job, bundle):
|
|
524
|
+
from protocol import skill_name
|
|
525
|
+
|
|
526
|
+
baseline = job.bundles[job.spec["baseline_condition"]]
|
|
527
|
+
descriptors = [path for path in job.spec["target"]["surfaces"]["skill"]
|
|
528
|
+
if Path(path).name == "SKILL.md"]
|
|
529
|
+
descriptors.extend(root + "/SKILL.md" for root in job.spec["source"].get("fixed_skill_roots", []))
|
|
530
|
+
files = {}
|
|
531
|
+
for path in descriptors:
|
|
532
|
+
if path not in bundle:
|
|
533
|
+
continue
|
|
534
|
+
root = Path(path).parent.as_posix()
|
|
535
|
+
name = skill_name(baseline[path]["content"])
|
|
536
|
+
for item, row in bundle.items():
|
|
537
|
+
if item.startswith(root + "/"):
|
|
538
|
+
destination = "/home/evolution/.agents/skills/" + name + "/" + item[len(root) + 1:]
|
|
539
|
+
files[destination] = encoded_file(row["content"], row["mode"])
|
|
540
|
+
return files
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def smoke_payload(job, case, purpose):
|
|
544
|
+
runtime = job.spec["runtime"]
|
|
545
|
+
repo = case["repo_path"]
|
|
546
|
+
views = job.case_files[case["case_id"]]
|
|
547
|
+
files = {}
|
|
548
|
+
for name, row in views["public_paths"].items():
|
|
549
|
+
files["/evolution-input/task/" + name] = encoded_file(
|
|
550
|
+
row["content"], row["mode"]
|
|
551
|
+
)
|
|
552
|
+
native = purpose == "probe"
|
|
553
|
+
baseline = job.bundles[job.spec["baseline_condition"]]
|
|
554
|
+
same_repository = (case["repo_id"], case["base_commit"]) == (
|
|
555
|
+
job.spec["target"]["repo_id"],
|
|
556
|
+
job.spec["target"]["base_commit"],
|
|
557
|
+
)
|
|
558
|
+
removed = []
|
|
559
|
+
if same_repository:
|
|
560
|
+
for name, row in baseline.items():
|
|
561
|
+
if native and name in job.spec["target"]["surfaces"]["skill"]:
|
|
562
|
+
removed.append(repo + "/" + name)
|
|
563
|
+
else:
|
|
564
|
+
files[repo + "/" + name] = encoded_file(row["content"], row["mode"])
|
|
565
|
+
configuration = codex_configuration(
|
|
566
|
+
runtime.get("model", "gpt-6.1-sol"), runtime.get("effort", "high"),
|
|
567
|
+
schema_version=job.spec["schema_version"],
|
|
568
|
+
)
|
|
569
|
+
if native:
|
|
570
|
+
files.update(native_skill_files(job, baseline))
|
|
571
|
+
if "model" not in runtime:
|
|
572
|
+
files["/home/evolution/.codex/auth.json"] = encoded_file(
|
|
573
|
+
encoded({"OPENAI_API_KEY": "isolation-sentinel-not-a-credential"}),
|
|
574
|
+
0o600,
|
|
575
|
+
)
|
|
576
|
+
args = codex_arguments(configuration, repo,
|
|
577
|
+
probe=isolation_script(case, schema_version=job.spec["schema_version"]))
|
|
578
|
+
if "model" in runtime:
|
|
579
|
+
startup = codex_arguments(
|
|
580
|
+
configuration,
|
|
581
|
+
repo,
|
|
582
|
+
prompt="Reply exactly RUNTIME_READY. Do not inspect or change files or use tools.",
|
|
583
|
+
)
|
|
584
|
+
args = ["bash", "-c", shlex.join(args) + " && " + shlex.join(startup)]
|
|
585
|
+
outputs = {
|
|
586
|
+
"instruction-receipt.json": "/evolution-control/instruction-receipt.json"
|
|
587
|
+
}
|
|
588
|
+
else:
|
|
589
|
+
for name, row in views["grader_paths"].items():
|
|
590
|
+
files["/" + name] = encoded_file(row["content"], row["mode"])
|
|
591
|
+
if purpose == "oracle":
|
|
592
|
+
for name, row in views["oracle_paths"].items():
|
|
593
|
+
files["/" + name] = encoded_file(row["content"], row["mode"])
|
|
594
|
+
script = (
|
|
595
|
+
"bash /solution/solve.sh && bash /tests/test.sh"
|
|
596
|
+
if purpose == "oracle"
|
|
597
|
+
else "bash /tests/test.sh"
|
|
598
|
+
)
|
|
599
|
+
args = ["bash", "-c", script]
|
|
600
|
+
outputs = {
|
|
601
|
+
"reward.txt": "/logs/verifier/reward.txt",
|
|
602
|
+
"instruction-receipt.json": "/evolution-control/instruction-receipt.json",
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
"image": case["image"],
|
|
606
|
+
"platform": case["platform"],
|
|
607
|
+
"repo_path": repo,
|
|
608
|
+
"base_commit": case["base_commit"],
|
|
609
|
+
"files": files,
|
|
610
|
+
"remove_paths": removed,
|
|
611
|
+
"needs_codex": native,
|
|
612
|
+
"codex_config": configuration if native else None,
|
|
613
|
+
"grading": not native,
|
|
614
|
+
"command": {
|
|
615
|
+
"args": args,
|
|
616
|
+
"cwd": repo,
|
|
617
|
+
"uid": 10000,
|
|
618
|
+
"gid": 10000,
|
|
619
|
+
"env": {
|
|
620
|
+
"HOME": "/home/evolution",
|
|
621
|
+
"CODEX_HOME": "/home/evolution/.codex",
|
|
622
|
+
"PATH": TASK_PATH,
|
|
623
|
+
"GOPATH": "/go",
|
|
624
|
+
"GOCACHE": "/tmp/evolution-go-cache",
|
|
625
|
+
"LANG": "C.UTF-8",
|
|
626
|
+
"USER": "evolution",
|
|
627
|
+
**(
|
|
628
|
+
{
|
|
629
|
+
"HTTP_PROXY": runtime["model_proxy"],
|
|
630
|
+
"HTTPS_PROXY": runtime["model_proxy"],
|
|
631
|
+
}
|
|
632
|
+
if native and "model_proxy" in runtime
|
|
633
|
+
else {}
|
|
634
|
+
),
|
|
635
|
+
},
|
|
636
|
+
"capture": {
|
|
637
|
+
"repo": repo,
|
|
638
|
+
"baseline_ref_file": "/evolution-control/base-tree.txt",
|
|
639
|
+
"excluded_paths": job.spec["target"]["surfaces"]["agents"],
|
|
640
|
+
"session_dir": "/home/evolution/.codex/sessions" if native else None,
|
|
641
|
+
"runtime_skills": native and "model" in runtime,
|
|
642
|
+
"files": outputs,
|
|
643
|
+
"optional_files": {}
|
|
644
|
+
if native
|
|
645
|
+
else {
|
|
646
|
+
"grade-output.json": "/logs/verifier/output.json",
|
|
647
|
+
"run-script-stdout.txt": "/logs/verifier/run-script-stdout.txt",
|
|
648
|
+
"run-script-stderr.txt": "/logs/verifier/run-script-stderr.txt",
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def trial_payload(job, case, bundle):
|
|
656
|
+
payload = smoke_payload(job, case, "probe")
|
|
657
|
+
files = payload["files"]
|
|
658
|
+
for name in list(files):
|
|
659
|
+
if name.startswith("/home/evolution/.agents/skills/"):
|
|
660
|
+
del files[name]
|
|
661
|
+
from protocol import task_contract
|
|
662
|
+
|
|
663
|
+
files.update(native_skill_files(job, bundle))
|
|
664
|
+
for path in job.spec["target"]["surfaces"]["agents"]:
|
|
665
|
+
destination = case["repo_path"] + "/" + path
|
|
666
|
+
files.pop(destination, None)
|
|
667
|
+
if path in bundle:
|
|
668
|
+
files[destination] = encoded_file(
|
|
669
|
+
bundle[path]["content"], bundle[path]["mode"]
|
|
670
|
+
)
|
|
671
|
+
else:
|
|
672
|
+
payload["remove_paths"].append(destination)
|
|
673
|
+
instruction = job.case_files[case["case_id"]]["public_paths"]["instruction.md"][
|
|
674
|
+
"content"
|
|
675
|
+
].decode("utf-8")
|
|
676
|
+
if job.spec.get("evaluation", {}).get("scoring") == "continuous_100_v1":
|
|
677
|
+
contract = task_contract(case)
|
|
678
|
+
files["/evolution-input/task-contract.md"] = encoded_file(contract, 0o444)
|
|
679
|
+
instruction += "\n\nAdditional frozen observable task requirements:\n" + contract.decode("utf-8")
|
|
680
|
+
payload["command"]["args"] = codex_arguments(
|
|
681
|
+
payload["codex_config"], case["repo_path"], prompt=instruction
|
|
682
|
+
)
|
|
683
|
+
return payload
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def grading_payload(job, case, patch):
|
|
687
|
+
payload = smoke_payload(job, case, "empty")
|
|
688
|
+
payload["files"]["/evolution-input/model.patch"] = encoded_file(patch)
|
|
689
|
+
payload["command"]["args"] = [
|
|
690
|
+
"bash",
|
|
691
|
+
"-c",
|
|
692
|
+
"git apply --binary /evolution-input/model.patch && bash /tests/test.sh"
|
|
693
|
+
if patch
|
|
694
|
+
else "bash /tests/test.sh",
|
|
695
|
+
]
|
|
696
|
+
payload["command"]["capture"]["optional_files"]["checks.json"] = (
|
|
697
|
+
"/logs/verifier/checks.json"
|
|
698
|
+
)
|
|
699
|
+
if job.spec["schema_version"] == 2:
|
|
700
|
+
rubric = json.loads(job.evaluation_files[job.spec["evaluation"]["rubrics"][case["case_id"]]]["content"])
|
|
701
|
+
names = sorted({name for c in rubric["criteria"] for name in c["context_paths"] if name.startswith("repo/")})
|
|
702
|
+
source = """
|
|
703
|
+
import json, pathlib, sys
|
|
704
|
+
root = pathlib.Path.cwd()
|
|
705
|
+
result = {'files': {}, 'omissions': []}
|
|
706
|
+
for name in json.loads(sys.argv[1]):
|
|
707
|
+
path = root / name.removeprefix('repo/')
|
|
708
|
+
try:
|
|
709
|
+
if '..' in path.relative_to(root).parts or any(p.is_symlink() for p in [path, *path.parents] if p != root):
|
|
710
|
+
raise ValueError('unsafe path')
|
|
711
|
+
result['files'][name] = path.read_text(encoding='utf-8')
|
|
712
|
+
except (OSError, ValueError, UnicodeError):
|
|
713
|
+
result['omissions'].append(name)
|
|
714
|
+
pathlib.Path('/logs/verifier/quality-context.json').write_text(json.dumps(result))
|
|
715
|
+
"""
|
|
716
|
+
collect = shlex.join(["python3", "-c", source, encoded(names).decode()])
|
|
717
|
+
verify = "bash /tests/test.sh"
|
|
718
|
+
observations = sorted({name for c in rubric["criteria"] for name in c["context_paths"]
|
|
719
|
+
if name.startswith("verifier/")})
|
|
720
|
+
if observations:
|
|
721
|
+
observe_source = """
|
|
722
|
+
import json, pathlib, sys
|
|
723
|
+
root = pathlib.Path('/logs/verifier/public')
|
|
724
|
+
result = {'files': {}, 'omissions': []}
|
|
725
|
+
for name in json.loads(sys.argv[1]):
|
|
726
|
+
path = root / name.removeprefix('verifier/')
|
|
727
|
+
try:
|
|
728
|
+
if '..' in path.relative_to(root).parts or any(p.is_symlink() for p in [path, *path.parents]):
|
|
729
|
+
raise ValueError('unsafe observation path')
|
|
730
|
+
result['files'][name] = path.read_bytes().decode('utf-8')
|
|
731
|
+
except (OSError, ValueError, UnicodeError):
|
|
732
|
+
result['omissions'].append(name)
|
|
733
|
+
pathlib.Path('/logs/verifier/public-context.json').write_text(json.dumps(result))
|
|
734
|
+
"""
|
|
735
|
+
observe = shlex.join(["python3", "-c", observe_source, encoded(observations).decode()])
|
|
736
|
+
verify = '{ bash /tests/test.sh; xgent_verifier_status=$?; ' + observe + '; exit "$xgent_verifier_status"; }'
|
|
737
|
+
payload["command"]["capture"]["files"]["public-context.json"] = "/logs/verifier/public-context.json"
|
|
738
|
+
payload["command"]["args"] = ["bash", "-c",
|
|
739
|
+
("git apply --binary /evolution-input/model.patch && " if patch else "") + collect + " && " + verify]
|
|
740
|
+
payload["command"]["capture"]["files"]["quality-context.json"] = "/logs/verifier/quality-context.json"
|
|
741
|
+
return payload
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def regression_payload(job, case, patch, checker):
|
|
745
|
+
payload = grading_payload(job, case, b"")
|
|
746
|
+
payload["files"]["/evolution-input/model.patch"] = encoded_file(patch)
|
|
747
|
+
payload["files"]["/evolution-input/checker.json"] = encoded_file(encoded(checker))
|
|
748
|
+
script = """
|
|
749
|
+
import json, pathlib, subprocess
|
|
750
|
+
checker = json.loads(pathlib.Path('/evolution-input/checker.json').read_text())
|
|
751
|
+
patch = '/evolution-input/model.patch'
|
|
752
|
+
base_tree = subprocess.check_output(['git','write-tree'],text=True).strip()
|
|
753
|
+
def run(argv):
|
|
754
|
+
p = subprocess.run(argv, capture_output=True, text=True, errors='replace')
|
|
755
|
+
return {'exit_code': p.returncode, 'stdout': p.stdout, 'stderr': p.stderr}
|
|
756
|
+
def reset():
|
|
757
|
+
subprocess.run(['git','read-tree','--reset','-u',base_tree],check=True)
|
|
758
|
+
subprocess.run(['git','clean','-fd'],check=True,capture_output=True)
|
|
759
|
+
result = {'status': 'unknown', 'level': None, 'reason': 'replay_setup_failed', 'base_tree': base_tree}
|
|
760
|
+
selection = [arg for path in checker['test_paths'] for arg in ['--include',path]]
|
|
761
|
+
selected = run(['git','apply','--numstat','--allow-empty',*selection,patch])
|
|
762
|
+
test_patch = run(['git','apply','--binary','--allow-empty',*selection,patch])
|
|
763
|
+
if test_patch['exit_code'] == 0 and selected['exit_code'] == 0:
|
|
764
|
+
if not selected['stdout'].strip():
|
|
765
|
+
result.update(status='valid',level=0,reason='no_changed_regression_test')
|
|
766
|
+
else:
|
|
767
|
+
old = run(checker['argv'])
|
|
768
|
+
reset()
|
|
769
|
+
applied = run(['git','apply','--binary',patch])
|
|
770
|
+
if applied['exit_code'] == 0:
|
|
771
|
+
fixed = run(checker['argv'])
|
|
772
|
+
old_text = old['stdout'] + old['stderr']
|
|
773
|
+
environment = ['ModuleNotFoundError:', 'ImportError while importing test module', 'command not found', 'collected 0 items']
|
|
774
|
+
attributed = old['exit_code'] == checker['expected_failure']['exit_code'] and checker['expected_failure']['contains'] in old_text
|
|
775
|
+
if not any(marker in old_text + fixed['stdout'] + fixed['stderr'] for marker in environment):
|
|
776
|
+
result.update(status='valid',level=int(attributed and fixed['exit_code']==0),reason='counterfactual_replay')
|
|
777
|
+
else:
|
|
778
|
+
result['reason'] = 'replay_environment_failure'
|
|
779
|
+
result.update(original=old,fixed=fixed,defect_attributed=attributed)
|
|
780
|
+
pathlib.Path('/logs/verifier/regression-replay.json').write_text(json.dumps(result))
|
|
781
|
+
"""
|
|
782
|
+
payload["command"]["args"] = ["python3", "-c", script]
|
|
783
|
+
payload["command"]["capture"]["files"] = {
|
|
784
|
+
"regression-replay.json": "/logs/verifier/regression-replay.json",
|
|
785
|
+
"instruction-receipt.json": "/evolution-control/instruction-receipt.json",
|
|
786
|
+
}
|
|
787
|
+
payload["command"]["capture"]["optional_files"] = {}
|
|
788
|
+
return payload
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def generation_payload(job, source, instruction, outputs, *, optimizer=False):
|
|
792
|
+
case = job.spec["cases"][0]
|
|
793
|
+
payload = smoke_payload(job, case, "probe")
|
|
794
|
+
runtime = job.spec["optimizer"] if optimizer else job.spec["runtime"]
|
|
795
|
+
configuration = codex_configuration(runtime["model"], runtime["effort"],
|
|
796
|
+
schema_version=job.spec["schema_version"])
|
|
797
|
+
payload.update(
|
|
798
|
+
repo_path="/work",
|
|
799
|
+
base_commit=None,
|
|
800
|
+
generated_workspace=True,
|
|
801
|
+
codex_config=configuration,
|
|
802
|
+
)
|
|
803
|
+
payload["files"] = {"/work/source.json": encoded_file(encoded(source))}
|
|
804
|
+
payload["remove_paths"] = []
|
|
805
|
+
if optimizer:
|
|
806
|
+
payload["files"]["/evolution-input/method.md"] = encoded_file(
|
|
807
|
+
job.method_files["method.md"]["content"]
|
|
808
|
+
)
|
|
809
|
+
payload["command"].update(
|
|
810
|
+
args=codex_arguments(configuration, "/work", prompt=instruction), cwd="/work"
|
|
811
|
+
)
|
|
812
|
+
payload["command"]["capture"] = {
|
|
813
|
+
"repo": None,
|
|
814
|
+
"excluded_paths": [],
|
|
815
|
+
"session_dir": "/home/evolution/.codex/sessions",
|
|
816
|
+
"runtime_skills": True,
|
|
817
|
+
"files": {
|
|
818
|
+
**{name: "/work/" + name for name in outputs},
|
|
819
|
+
"instruction-receipt.json": "/evolution-control/instruction-receipt.json",
|
|
820
|
+
},
|
|
821
|
+
}
|
|
822
|
+
return payload
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def typesafe_payload(job, request, *, long_context=False):
|
|
826
|
+
from typesafe_worker import validate_request
|
|
827
|
+
|
|
828
|
+
validate_request(request)
|
|
829
|
+
if type(long_context) is not bool:
|
|
830
|
+
raise RemoteError("invalid TypeSafe long-context setting")
|
|
831
|
+
payload = generation_payload(job, {}, "", [])
|
|
832
|
+
credential = "/home/evolution/.typesafe/api-key"
|
|
833
|
+
payload.update(needs_codex=False, needs_typesafe=True, codex_config=None)
|
|
834
|
+
payload["files"] = {
|
|
835
|
+
"/work/request.json": encoded_file(encoded(request)),
|
|
836
|
+
"/work/typesafe_worker.py": encoded_file(job.worker_files["typesafe_worker.py"]["content"]),
|
|
837
|
+
}
|
|
838
|
+
payload["command"].update(
|
|
839
|
+
args=["python3", "/work/typesafe_worker.py", "--request", "/work/request.json",
|
|
840
|
+
"--credential", credential, "--output", "/work/auxiliary-response.json",
|
|
841
|
+
"--timeout", str(max(1, job.spec["budget"]["per_trial_timeout_sec"] - 10))],
|
|
842
|
+
typesafe_credential=credential,
|
|
843
|
+
)
|
|
844
|
+
if long_context:
|
|
845
|
+
payload["command"]["args"].append("--long-context")
|
|
846
|
+
payload["command"]["capture"] = {
|
|
847
|
+
"repo": None, "excluded_paths": [], "session_dir": None,
|
|
848
|
+
"files": {"auxiliary-response.json": "/work/auxiliary-response.json",
|
|
849
|
+
"instruction-receipt.json": "/evolution-control/instruction-receipt.json"},
|
|
850
|
+
}
|
|
851
|
+
return payload
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def prepare_typesafe_credential(runtime, execution, payload):
|
|
855
|
+
control = host_control(execution)
|
|
856
|
+
packet = read_json(control / "payload.json")
|
|
857
|
+
if execution["role"] != "grader" or not packet.get("needs_typesafe") or packet.get("needs_codex"):
|
|
858
|
+
raise RemoteError("credential preparation requires the registered API worker")
|
|
859
|
+
if packet["command"].get("typesafe_credential") != "/home/evolution/.typesafe/api-key":
|
|
860
|
+
raise RemoteError("API credential path differs")
|
|
861
|
+
if set(payload) != {"key"} or not isinstance(payload["key"], str) or not payload["key"] or any(
|
|
862
|
+
character.isspace() for character in payload["key"]
|
|
863
|
+
):
|
|
864
|
+
raise RemoteError("API credential is unavailable")
|
|
865
|
+
if (control / "typesafe-credential.json").exists():
|
|
866
|
+
raise UnresolvedError("API credential preparation was already attempted; query the original execution")
|
|
867
|
+
save_json(control / "typesafe-credential.json", {"prepared": False, "destroyed": False})
|
|
868
|
+
# The secret uses its own stdin operation, never create_resource's persisted payload.
|
|
869
|
+
key_path = control / "typesafe.key"
|
|
870
|
+
save_bytes(key_path, payload["key"].encode())
|
|
871
|
+
key_path.chmod(0o600)
|
|
872
|
+
container_exec(runtime, execution, ["python3", "-c",
|
|
873
|
+
"import os,pathlib; p=pathlib.Path('/home/evolution/.typesafe'); "
|
|
874
|
+
"p.mkdir(mode=0o700,exist_ok=True); os.chown(p,10000,10000); os.chmod(p,0o700)"])
|
|
875
|
+
container_files(runtime, execution, {
|
|
876
|
+
"/home/evolution/.typesafe/api-key": encoded_file(key_path.read_bytes(), 0o600),
|
|
877
|
+
"/evolution-control/redaction.json": encoded_file(encoded([payload["key"]]), 0o600),
|
|
878
|
+
})
|
|
879
|
+
container_exec(runtime, execution, ["chown", "10000:10000", "/home/evolution/.typesafe/api-key"])
|
|
880
|
+
key_path.unlink()
|
|
881
|
+
save_json(control / "typesafe-credential.json", {"prepared": True, "destroyed": False})
|
|
882
|
+
return {"prepared": True}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def destroy_typesafe_credential(runtime, execution, resource):
|
|
886
|
+
control = host_control(execution)
|
|
887
|
+
receipt = control / "typesafe-credential.json"
|
|
888
|
+
if not receipt.exists():
|
|
889
|
+
return True
|
|
890
|
+
(control / "typesafe.key").unlink(missing_ok=True)
|
|
891
|
+
if resource is not None:
|
|
892
|
+
container_exec(runtime, execution, ["python3", "-c",
|
|
893
|
+
"import pathlib; pathlib.Path('/home/evolution/.typesafe/api-key').unlink(missing_ok=True); "
|
|
894
|
+
"pathlib.Path('/evolution-control/redaction.json').unlink(missing_ok=True)"])
|
|
895
|
+
save_json(receipt, {"prepared": read_json(receipt)["prepared"], "destroyed": True})
|
|
896
|
+
return True
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def host_environment(runtime):
|
|
900
|
+
return {
|
|
901
|
+
"HOME": str(Path.home()),
|
|
902
|
+
"PATH": str(Path(runtime["docker_binary"]).parent)
|
|
903
|
+
+ ":/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
|
|
904
|
+
"LANG": "C.UTF-8",
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def docker(runtime, arguments, *, content=None, timeout=60, check=True):
|
|
909
|
+
result = subprocess.run(
|
|
910
|
+
[runtime["docker_binary"], *arguments],
|
|
911
|
+
input=content,
|
|
912
|
+
capture_output=True,
|
|
913
|
+
env=host_environment(runtime),
|
|
914
|
+
timeout=timeout,
|
|
915
|
+
)
|
|
916
|
+
if check and result.returncode:
|
|
917
|
+
raise RemoteError("remote Docker command failed")
|
|
918
|
+
return result
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def engine_identity(runtime):
|
|
922
|
+
result = docker(
|
|
923
|
+
runtime,
|
|
924
|
+
[
|
|
925
|
+
"info",
|
|
926
|
+
"--format",
|
|
927
|
+
'{"id":{{json .ID}},"version":{{json .ServerVersion}},"architecture":{{json .Architecture}}}',
|
|
928
|
+
],
|
|
929
|
+
)
|
|
930
|
+
value = json.loads(result.stdout)
|
|
931
|
+
if value["version"] != runtime["docker_version"]:
|
|
932
|
+
raise UnresolvedError("Docker version differs from the frozen runtime")
|
|
933
|
+
architecture = {
|
|
934
|
+
"arm64": "aarch64",
|
|
935
|
+
"aarch64": "aarch64",
|
|
936
|
+
"amd64": "x86_64",
|
|
937
|
+
"x86_64": "x86_64",
|
|
938
|
+
}.get(value["architecture"])
|
|
939
|
+
if architecture != runtime["codex_architecture"]:
|
|
940
|
+
raise UnresolvedError("Codex architecture differs from the Docker kernel")
|
|
941
|
+
return value["id"]
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def host_control(execution):
|
|
945
|
+
task_id = str(uuid.UUID(execution["remote_task_id"]))
|
|
946
|
+
if (
|
|
947
|
+
task_id != execution["remote_task_id"]
|
|
948
|
+
or execution["sandbox_name"] != "evolve-" + task_id
|
|
949
|
+
):
|
|
950
|
+
raise UnresolvedError("invalid registered execution identity")
|
|
951
|
+
root = Path(execution["remote_root"]) / "executions" / task_id / "control"
|
|
952
|
+
if str(root / "receipt.json") != execution["receipt_path"]:
|
|
953
|
+
raise UnresolvedError("registered receipt path differs")
|
|
954
|
+
if root.is_symlink():
|
|
955
|
+
raise UnresolvedError("execution control directory cannot be a symlink")
|
|
956
|
+
return root
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def owned_resource(runtime, execution):
|
|
960
|
+
identity = engine_identity(runtime)
|
|
961
|
+
if execution["engine_id"] is not None and execution["engine_id"] != identity:
|
|
962
|
+
raise UnresolvedError("registered Docker engine differs")
|
|
963
|
+
result = docker(
|
|
964
|
+
runtime,
|
|
965
|
+
["inspect", execution["sandbox_id"] or execution["sandbox_name"]],
|
|
966
|
+
check=False,
|
|
967
|
+
)
|
|
968
|
+
if result.returncode:
|
|
969
|
+
# Confirm an absent name with a successful engine query; arbitrary
|
|
970
|
+
# transport and daemon failures must not be interpreted as deletion.
|
|
971
|
+
names = docker(
|
|
972
|
+
runtime,
|
|
973
|
+
[
|
|
974
|
+
"ps",
|
|
975
|
+
"-a",
|
|
976
|
+
"--no-trunc",
|
|
977
|
+
"--filter",
|
|
978
|
+
"name=^/" + execution["sandbox_name"] + "$",
|
|
979
|
+
"--format",
|
|
980
|
+
"{{.ID}}",
|
|
981
|
+
],
|
|
982
|
+
)
|
|
983
|
+
if names.stdout.strip():
|
|
984
|
+
raise UnresolvedError("resource inspection is unresolved")
|
|
985
|
+
return None
|
|
986
|
+
resource = json.loads(result.stdout)[0]
|
|
987
|
+
labels = resource["Config"].get("Labels") or {}
|
|
988
|
+
if (
|
|
989
|
+
labels.get("xgent.execution") != execution["remote_task_id"]
|
|
990
|
+
or labels.get("xgent.input") != execution["input_hash"]
|
|
991
|
+
):
|
|
992
|
+
raise UnresolvedError("resource ownership labels differ")
|
|
993
|
+
if (
|
|
994
|
+
execution["sandbox_id"] is not None
|
|
995
|
+
and resource["Id"] != execution["sandbox_id"]
|
|
996
|
+
):
|
|
997
|
+
raise UnresolvedError("registered container ID differs")
|
|
998
|
+
return resource
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def container_exec(
|
|
1002
|
+
runtime, execution, args, *, user="root", cwd=None, env=None, timeout=60, check=True
|
|
1003
|
+
):
|
|
1004
|
+
arguments = ["exec", "--user", str(user)]
|
|
1005
|
+
if cwd:
|
|
1006
|
+
arguments += ["--workdir", cwd]
|
|
1007
|
+
for key, value in (env or {}).items():
|
|
1008
|
+
arguments += ["--env", key + "=" + value]
|
|
1009
|
+
return docker(
|
|
1010
|
+
runtime,
|
|
1011
|
+
[*arguments, execution["sandbox_id"], *args],
|
|
1012
|
+
timeout=timeout,
|
|
1013
|
+
check=check,
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def container_files(runtime, execution, files):
|
|
1018
|
+
archive = io.BytesIO()
|
|
1019
|
+
with tarfile.open(fileobj=archive, mode="w") as stream:
|
|
1020
|
+
for name, row in files.items():
|
|
1021
|
+
path = Path(name)
|
|
1022
|
+
if not path.is_absolute() or ".." in path.parts:
|
|
1023
|
+
raise RemoteError(
|
|
1024
|
+
"container file path is outside the declared namespace"
|
|
1025
|
+
)
|
|
1026
|
+
raw = base64.b64decode(row["data"], validate=True)
|
|
1027
|
+
info = tarfile.TarInfo(name.lstrip("/"))
|
|
1028
|
+
info.size = len(raw)
|
|
1029
|
+
info.mode = row.get("mode", 0o644)
|
|
1030
|
+
stream.addfile(info, io.BytesIO(raw))
|
|
1031
|
+
docker(
|
|
1032
|
+
runtime, ["cp", "-", execution["sandbox_id"] + ":/"], content=archive.getvalue()
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def encoded_file(content, mode=0o644):
|
|
1037
|
+
return {"data": base64.b64encode(content).decode("ascii"), "mode": mode}
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def install_codex_archive(destination, version, architecture):
|
|
1041
|
+
label = version + "-linux-" + architecture
|
|
1042
|
+
url = "https://registry.npmjs.org/@openai%2Fcodex/" + label
|
|
1043
|
+
with urllib.request.urlopen(url, timeout=120) as response:
|
|
1044
|
+
metadata = json.load(response)
|
|
1045
|
+
tarball = "https://registry.npmjs.org/@openai/codex/-/codex-" + label + ".tgz"
|
|
1046
|
+
if (
|
|
1047
|
+
metadata.get("name") != "@openai/codex"
|
|
1048
|
+
or metadata.get("version") != label
|
|
1049
|
+
or metadata.get("os") != ["linux"]
|
|
1050
|
+
or metadata.get("cpu") != [architecture]
|
|
1051
|
+
or metadata["dist"]["tarball"] != tarball
|
|
1052
|
+
):
|
|
1053
|
+
raise RemoteError("fixed Codex package metadata differs")
|
|
1054
|
+
with urllib.request.urlopen(tarball, timeout=120) as response:
|
|
1055
|
+
raw = response.read()
|
|
1056
|
+
integrity = "sha512-" + base64.b64encode(hashlib.sha512(raw).digest()).decode()
|
|
1057
|
+
if metadata["dist"]["integrity"] != integrity:
|
|
1058
|
+
raise RemoteError("fixed Codex package integrity differs")
|
|
1059
|
+
destination = Path(destination)
|
|
1060
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
1061
|
+
if destination.exists():
|
|
1062
|
+
raise RemoteError("native Codex installation destination already exists")
|
|
1063
|
+
with tempfile.TemporaryDirectory(
|
|
1064
|
+
prefix=".codex-", dir=destination.parent
|
|
1065
|
+
) as temporary:
|
|
1066
|
+
staged = Path(temporary) / "package"
|
|
1067
|
+
with tarfile.open(fileobj=io.BytesIO(raw), mode="r:gz") as archive:
|
|
1068
|
+
for member in archive:
|
|
1069
|
+
path = Path(member.name)
|
|
1070
|
+
if (
|
|
1071
|
+
path.is_absolute()
|
|
1072
|
+
or ".." in path.parts
|
|
1073
|
+
or not path.parts
|
|
1074
|
+
or path.parts[0] != "package"
|
|
1075
|
+
):
|
|
1076
|
+
raise RemoteError(
|
|
1077
|
+
"native Codex archive path is outside its package"
|
|
1078
|
+
)
|
|
1079
|
+
if member.isdir():
|
|
1080
|
+
continue
|
|
1081
|
+
if not member.isfile():
|
|
1082
|
+
raise RemoteError("native Codex archive contains a nonregular file")
|
|
1083
|
+
target = staged.joinpath(*path.parts[1:])
|
|
1084
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
1085
|
+
target.write_bytes(archive.extractfile(member).read())
|
|
1086
|
+
target.chmod(member.mode & 0o777)
|
|
1087
|
+
staged.rename(destination)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def instruction_inventory(repo, home, expected):
|
|
1091
|
+
repo, home = Path(repo), Path(home)
|
|
1092
|
+
paths = set()
|
|
1093
|
+
for root, directories, files in os.walk(repo, followlinks=False):
|
|
1094
|
+
directories[:] = [name for name in directories if name != ".git"]
|
|
1095
|
+
paths.update(
|
|
1096
|
+
Path(root) / name
|
|
1097
|
+
for name in files
|
|
1098
|
+
if name in {"AGENTS.md", "AGENTS.override.md", "SKILL.md"}
|
|
1099
|
+
)
|
|
1100
|
+
for root in [repo, *repo.parents]:
|
|
1101
|
+
paths.update(
|
|
1102
|
+
root / name
|
|
1103
|
+
for name in ["AGENTS.md", "AGENTS.override.md"]
|
|
1104
|
+
if (root / name).exists()
|
|
1105
|
+
)
|
|
1106
|
+
paths.update(
|
|
1107
|
+
path for path in (root / ".agents/skills").rglob("*") if path.is_file()
|
|
1108
|
+
)
|
|
1109
|
+
for root in [home / ".agents/skills", home / ".codex/skills", Path("/etc/codex")]:
|
|
1110
|
+
paths.update(path for path in root.rglob("*") if path.is_file())
|
|
1111
|
+
paths.update(
|
|
1112
|
+
home / ".codex" / name
|
|
1113
|
+
for name in ["AGENTS.md", "AGENTS.override.md", "memory.md"]
|
|
1114
|
+
if (home / ".codex" / name).exists()
|
|
1115
|
+
)
|
|
1116
|
+
for path in list(paths):
|
|
1117
|
+
if path.name == "SKILL.md":
|
|
1118
|
+
paths.update(item for item in path.parent.rglob("*") if item.is_file())
|
|
1119
|
+
targets = set()
|
|
1120
|
+
for name in expected:
|
|
1121
|
+
path = Path(name)
|
|
1122
|
+
if path.name == "SKILL.md" and path.is_relative_to(home):
|
|
1123
|
+
match = re.search(
|
|
1124
|
+
rb"(?m)^name:[ \t]*['\"]?([a-z0-9][a-z0-9-]*)", path.read_bytes()
|
|
1125
|
+
)
|
|
1126
|
+
if match:
|
|
1127
|
+
targets.add(match[1])
|
|
1128
|
+
sources, gaps = [], []
|
|
1129
|
+
for path in sorted(paths):
|
|
1130
|
+
if path.is_symlink() or any(
|
|
1131
|
+
parent.is_symlink()
|
|
1132
|
+
for parent in path.parents
|
|
1133
|
+
if parent.is_relative_to(repo) or parent.is_relative_to(home)
|
|
1134
|
+
):
|
|
1135
|
+
gaps.append("symlink_instruction:" + str(path))
|
|
1136
|
+
continue
|
|
1137
|
+
content = path.read_bytes()
|
|
1138
|
+
hashed = digest(content)
|
|
1139
|
+
if str(path) in expected and hashed == expected[str(path)]["sha256"]:
|
|
1140
|
+
origin = "injected"
|
|
1141
|
+
elif path.is_relative_to(repo):
|
|
1142
|
+
original = subprocess.run(
|
|
1143
|
+
[
|
|
1144
|
+
"git",
|
|
1145
|
+
"-c",
|
|
1146
|
+
"safe.directory=" + str(repo),
|
|
1147
|
+
"-C",
|
|
1148
|
+
str(repo),
|
|
1149
|
+
"show",
|
|
1150
|
+
"HEAD:" + path.relative_to(repo).as_posix(),
|
|
1151
|
+
],
|
|
1152
|
+
capture_output=True,
|
|
1153
|
+
)
|
|
1154
|
+
origin = (
|
|
1155
|
+
"repository_background"
|
|
1156
|
+
if original.returncode == 0 and original.stdout == content
|
|
1157
|
+
else "unknown"
|
|
1158
|
+
)
|
|
1159
|
+
else:
|
|
1160
|
+
origin = "unknown"
|
|
1161
|
+
if origin == "unknown":
|
|
1162
|
+
gaps.append("undeclared_instruction:" + str(path))
|
|
1163
|
+
if path.name == "SKILL.md" and str(path) not in expected:
|
|
1164
|
+
match = re.search(rb"(?m)^name:[ \t]*['\"]?([a-z0-9][a-z0-9-]*)", content)
|
|
1165
|
+
if match and match[1] in targets:
|
|
1166
|
+
gaps.append("duplicate_target_skill:" + str(path))
|
|
1167
|
+
sources.append({"path": str(path), "sha256": hashed, "origin": origin})
|
|
1168
|
+
return {"sources": sources, "gaps": sorted(set(gaps))}
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
def credential_values(value):
|
|
1172
|
+
result = []
|
|
1173
|
+
if isinstance(value, dict):
|
|
1174
|
+
for key, item in value.items():
|
|
1175
|
+
if (
|
|
1176
|
+
isinstance(item, str)
|
|
1177
|
+
and len(item) >= 16
|
|
1178
|
+
and any(word in key.upper() for word in ["TOKEN", "API_KEY", "SECRET"])
|
|
1179
|
+
):
|
|
1180
|
+
result.append(item)
|
|
1181
|
+
else:
|
|
1182
|
+
result.extend(credential_values(item))
|
|
1183
|
+
elif isinstance(value, list):
|
|
1184
|
+
for item in value:
|
|
1185
|
+
result.extend(credential_values(item))
|
|
1186
|
+
return result
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
def tooling_manifest(directory):
|
|
1190
|
+
directory = Path(directory).resolve(strict=True)
|
|
1191
|
+
result = []
|
|
1192
|
+
for path in sorted(directory.rglob("*")):
|
|
1193
|
+
mode = path.lstat().st_mode
|
|
1194
|
+
entry = {
|
|
1195
|
+
"path": path.relative_to(directory).as_posix(),
|
|
1196
|
+
"mode": stat.S_IMODE(mode),
|
|
1197
|
+
}
|
|
1198
|
+
if path.is_symlink():
|
|
1199
|
+
if not path.resolve().is_relative_to(directory):
|
|
1200
|
+
raise UnresolvedError("cached tooling link escapes its package")
|
|
1201
|
+
entry["link"] = os.readlink(path)
|
|
1202
|
+
elif path.is_file():
|
|
1203
|
+
entry["sha256"] = digest(path.read_bytes())
|
|
1204
|
+
elif path.is_dir():
|
|
1205
|
+
continue
|
|
1206
|
+
else:
|
|
1207
|
+
raise UnresolvedError("cached tooling contains a special file")
|
|
1208
|
+
result.append(entry)
|
|
1209
|
+
if not result:
|
|
1210
|
+
raise UnresolvedError("cached tooling package is empty")
|
|
1211
|
+
return result
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def codex_configuration(model="gpt-6.1-sol", effort="high", *, schema_version=1):
|
|
1215
|
+
configuration = {
|
|
1216
|
+
"model": model,
|
|
1217
|
+
"model_reasoning_effort": effort,
|
|
1218
|
+
"approval_policy": "never",
|
|
1219
|
+
"default_permissions": "evolution",
|
|
1220
|
+
"web_search": "disabled",
|
|
1221
|
+
"mcp_servers": {},
|
|
1222
|
+
"notify": [],
|
|
1223
|
+
"features": {
|
|
1224
|
+
"multi_agent": False,
|
|
1225
|
+
"multi_agent_v2": False,
|
|
1226
|
+
"plugins": False,
|
|
1227
|
+
"apps": False,
|
|
1228
|
+
"shell_snapshot": False,
|
|
1229
|
+
"network_proxy": True,
|
|
1230
|
+
},
|
|
1231
|
+
"shell_environment_policy": {
|
|
1232
|
+
"inherit": "none",
|
|
1233
|
+
"ignore_default_excludes": False,
|
|
1234
|
+
"experimental_use_profile": False,
|
|
1235
|
+
"set": {
|
|
1236
|
+
"HOME": "/home/evolution",
|
|
1237
|
+
"PATH": TASK_PATH,
|
|
1238
|
+
"GOPATH": "/go",
|
|
1239
|
+
"GOCACHE": "/tmp/evolution-go-cache",
|
|
1240
|
+
"LANG": "C.UTF-8",
|
|
1241
|
+
},
|
|
1242
|
+
},
|
|
1243
|
+
"permissions": {
|
|
1244
|
+
"evolution": {
|
|
1245
|
+
"filesystem": {
|
|
1246
|
+
":minimal": "read",
|
|
1247
|
+
":workspace_roots": "write",
|
|
1248
|
+
"/tmp": "write",
|
|
1249
|
+
"/usr/local": "read",
|
|
1250
|
+
"/opt/evolution-codex": "read",
|
|
1251
|
+
"/go": "write",
|
|
1252
|
+
"/home/evolution/.nvm": "read",
|
|
1253
|
+
"/home/evolution/.agents": "read",
|
|
1254
|
+
"/evolution-input": "read",
|
|
1255
|
+
"/home/evolution/.codex": "deny",
|
|
1256
|
+
"/evolution-control": "deny",
|
|
1257
|
+
"/tests": "deny",
|
|
1258
|
+
"/solution": "deny",
|
|
1259
|
+
},
|
|
1260
|
+
"network": {
|
|
1261
|
+
"enabled": True,
|
|
1262
|
+
"mode": "limited",
|
|
1263
|
+
"allow_local_binding": False,
|
|
1264
|
+
"domains": {"localhost": "allow", "127.0.0.1": "allow"},
|
|
1265
|
+
},
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
}
|
|
1269
|
+
if schema_version == 2:
|
|
1270
|
+
filesystem = configuration["permissions"]["evolution"]["filesystem"]
|
|
1271
|
+
# The managed proxy writes public CA bundles beside private Codex state.
|
|
1272
|
+
# A parent deny also hides the injected public CA from child tools.
|
|
1273
|
+
del filesystem["/home/evolution/.codex"]
|
|
1274
|
+
# A directory component keeps rg's filename globs from recursing into
|
|
1275
|
+
# hidden state directories and layering a second mask over their files.
|
|
1276
|
+
filesystem.update({path: "deny" for path in (
|
|
1277
|
+
"/home/evolution/[.]codex/auth*",
|
|
1278
|
+
"/home/evolution/[.]codex/*.json*",
|
|
1279
|
+
"/home/evolution/[.]codex/config*.toml",
|
|
1280
|
+
"/home/evolution/.codex/.env",
|
|
1281
|
+
"/home/evolution/.codex/sessions",
|
|
1282
|
+
"/home/evolution/.codex/archived_sessions",
|
|
1283
|
+
"/home/evolution/.codex/log",
|
|
1284
|
+
"/home/evolution/[.]codex/*.sqlite*",
|
|
1285
|
+
"/home/evolution/[.]codex/.tmp*",
|
|
1286
|
+
)})
|
|
1287
|
+
filesystem["/home/evolution/.codex/proxy/**"] = "read"
|
|
1288
|
+
return configuration
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
def toml_value(value):
|
|
1292
|
+
if isinstance(value, dict):
|
|
1293
|
+
return (
|
|
1294
|
+
"{"
|
|
1295
|
+
+ ",".join(
|
|
1296
|
+
json.dumps(key) + "=" + toml_value(item)
|
|
1297
|
+
for key, item in sorted(value.items())
|
|
1298
|
+
)
|
|
1299
|
+
+ "}"
|
|
1300
|
+
)
|
|
1301
|
+
return json.dumps(value, ensure_ascii=False, allow_nan=False)
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
def codex_arguments(configuration, cwd, *, prompt=None, probe=None):
|
|
1305
|
+
overrides = [
|
|
1306
|
+
part
|
|
1307
|
+
for key, value in sorted(configuration.items())
|
|
1308
|
+
for part in ["-c", key + "=" + toml_value(value)]
|
|
1309
|
+
]
|
|
1310
|
+
if probe is not None:
|
|
1311
|
+
return [
|
|
1312
|
+
"/usr/local/bin/codex",
|
|
1313
|
+
"sandbox",
|
|
1314
|
+
"-C",
|
|
1315
|
+
cwd,
|
|
1316
|
+
"-P",
|
|
1317
|
+
"evolution",
|
|
1318
|
+
*overrides,
|
|
1319
|
+
"--",
|
|
1320
|
+
"python3",
|
|
1321
|
+
"-c",
|
|
1322
|
+
probe,
|
|
1323
|
+
]
|
|
1324
|
+
return [
|
|
1325
|
+
"/usr/local/bin/codex",
|
|
1326
|
+
"exec",
|
|
1327
|
+
"--strict-config",
|
|
1328
|
+
"--ignore-user-config",
|
|
1329
|
+
"--ignore-rules",
|
|
1330
|
+
"--skip-git-repo-check",
|
|
1331
|
+
"--json",
|
|
1332
|
+
"-C",
|
|
1333
|
+
cwd,
|
|
1334
|
+
*overrides,
|
|
1335
|
+
"--",
|
|
1336
|
+
prompt,
|
|
1337
|
+
]
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def create_resource(runtime, execution, payload):
|
|
1341
|
+
if (
|
|
1342
|
+
digest(encoded(payload)) != execution["input_hash"]
|
|
1343
|
+
or digest(encoded(payload["command"])) != execution["command_hash"]
|
|
1344
|
+
):
|
|
1345
|
+
raise RemoteError("submitted inputs differ from their frozen hashes")
|
|
1346
|
+
if payload["command"]["uid"] != 10000 or payload["command"]["gid"] != 10000:
|
|
1347
|
+
raise RemoteError("task command must use the unprivileged task identity")
|
|
1348
|
+
control = host_control(execution)
|
|
1349
|
+
control.mkdir(mode=0o700, parents=True, exist_ok=False)
|
|
1350
|
+
save_json(control / "receipt.json", execution)
|
|
1351
|
+
save_json(control / "runtime.json", runtime)
|
|
1352
|
+
save_json(control / "payload.json", payload)
|
|
1353
|
+
save_bytes(control / "remote.py", Path(__file__).read_bytes())
|
|
1354
|
+
identity = engine_identity(runtime)
|
|
1355
|
+
present = docker(runtime, ["image", "inspect", payload["image"]], check=False)
|
|
1356
|
+
if present.returncode:
|
|
1357
|
+
docker(
|
|
1358
|
+
runtime,
|
|
1359
|
+
["pull", "--platform", payload["platform"], payload["image"]],
|
|
1360
|
+
timeout=300,
|
|
1361
|
+
)
|
|
1362
|
+
remaining = int(execution["resource_deadline"] - time.time())
|
|
1363
|
+
if remaining <= 0:
|
|
1364
|
+
raise RemoteError("run deadline reached before resource creation")
|
|
1365
|
+
args = [
|
|
1366
|
+
"run",
|
|
1367
|
+
"--detach",
|
|
1368
|
+
"--init",
|
|
1369
|
+
"--name",
|
|
1370
|
+
execution["sandbox_name"],
|
|
1371
|
+
"--platform",
|
|
1372
|
+
payload["platform"],
|
|
1373
|
+
"--label",
|
|
1374
|
+
"xgent.execution=" + execution["remote_task_id"],
|
|
1375
|
+
"--label",
|
|
1376
|
+
"xgent.input=" + execution["input_hash"],
|
|
1377
|
+
"--cpus",
|
|
1378
|
+
"1",
|
|
1379
|
+
"--memory",
|
|
1380
|
+
"4g",
|
|
1381
|
+
"--pids-limit",
|
|
1382
|
+
"512",
|
|
1383
|
+
"--ulimit",
|
|
1384
|
+
"core=0",
|
|
1385
|
+
"--security-opt",
|
|
1386
|
+
"no-new-privileges",
|
|
1387
|
+
"--cap-drop",
|
|
1388
|
+
"NET_RAW",
|
|
1389
|
+
"--cap-add",
|
|
1390
|
+
"NET_ADMIN",
|
|
1391
|
+
"--security-opt",
|
|
1392
|
+
"seccomp=unconfined",
|
|
1393
|
+
"--security-opt",
|
|
1394
|
+
"apparmor=" + runtime.get("apparmor_profile", "unconfined"),
|
|
1395
|
+
"--entrypoint",
|
|
1396
|
+
"/bin/sh",
|
|
1397
|
+
payload["image"],
|
|
1398
|
+
"-c",
|
|
1399
|
+
"sleep " + str(remaining),
|
|
1400
|
+
]
|
|
1401
|
+
created = docker(runtime, args, timeout=300)
|
|
1402
|
+
execution.update(
|
|
1403
|
+
sandbox_id=created.stdout.decode().strip(),
|
|
1404
|
+
engine_id=identity,
|
|
1405
|
+
submission_status="resource_created",
|
|
1406
|
+
)
|
|
1407
|
+
save_json(control / "receipt.json", execution)
|
|
1408
|
+
resource = owned_resource(runtime, execution)
|
|
1409
|
+
if resource.get("AppArmorProfile") != runtime.get("apparmor_profile", "unconfined"):
|
|
1410
|
+
raise RemoteError(
|
|
1411
|
+
"actual container AppArmor profile differs from the frozen runtime"
|
|
1412
|
+
)
|
|
1413
|
+
if any(item["Type"] == "bind" for item in resource.get("Mounts", [])):
|
|
1414
|
+
raise RemoteError("task container has an undeclared host bind mount")
|
|
1415
|
+
removed = payload.get("remove_paths", [])
|
|
1416
|
+
root = Path(payload["repo_path"])
|
|
1417
|
+
if any(
|
|
1418
|
+
not Path(name).is_relative_to(root)
|
|
1419
|
+
or Path(name) == root
|
|
1420
|
+
or ".." in Path(name).parts
|
|
1421
|
+
or name in payload["files"]
|
|
1422
|
+
for name in removed
|
|
1423
|
+
):
|
|
1424
|
+
raise RemoteError(
|
|
1425
|
+
"input removal is outside the task repository or conflicts with staged bytes"
|
|
1426
|
+
)
|
|
1427
|
+
if removed:
|
|
1428
|
+
container_exec(
|
|
1429
|
+
runtime,
|
|
1430
|
+
execution,
|
|
1431
|
+
[
|
|
1432
|
+
"python3",
|
|
1433
|
+
"-c",
|
|
1434
|
+
"import json,pathlib,sys; "
|
|
1435
|
+
"[pathlib.Path(name).unlink(missing_ok=True) for name in json.loads(sys.argv[1])]",
|
|
1436
|
+
encoded(removed).decode(),
|
|
1437
|
+
],
|
|
1438
|
+
)
|
|
1439
|
+
files = dict(payload["files"])
|
|
1440
|
+
files.update(
|
|
1441
|
+
{
|
|
1442
|
+
"/evolution-control/remote.py": encoded_file(Path(__file__).read_bytes()),
|
|
1443
|
+
"/evolution-control/command.json": encoded_file(
|
|
1444
|
+
encoded(payload["command"])
|
|
1445
|
+
),
|
|
1446
|
+
"/evolution-control/payload.json": encoded_file(encoded(payload)),
|
|
1447
|
+
"/evolution-control/receipt.json": encoded_file(encoded(execution)),
|
|
1448
|
+
}
|
|
1449
|
+
)
|
|
1450
|
+
container_files(runtime, execution, files)
|
|
1451
|
+
setup = (
|
|
1452
|
+
"import os,pathlib; "
|
|
1453
|
+
"p=pathlib.Path('/home/evolution');p.mkdir(parents=True,exist_ok=True);os.chown(p,10000,10000); "
|
|
1454
|
+
"os.chmod('/evolution-control',0o700)"
|
|
1455
|
+
)
|
|
1456
|
+
container_exec(runtime, execution, ["python3", "-c", setup])
|
|
1457
|
+
basic = """
|
|
1458
|
+
import base64, hashlib, json, os, pathlib, subprocess, sys
|
|
1459
|
+
packet = json.loads(pathlib.Path('/evolution-control/payload.json').read_text())
|
|
1460
|
+
repo = pathlib.Path(packet['repo_path'])
|
|
1461
|
+
if packet.get('generated_workspace'):
|
|
1462
|
+
subprocess.run(['git','-C',str(repo),'init','-q'],check=True)
|
|
1463
|
+
subprocess.run(['git','-C',str(repo),'add','-A'],check=True)
|
|
1464
|
+
subprocess.run(['git','-C',str(repo),'-c','user.name=Evaluation','-c','user.email=evaluation@invalid','-c','core.hooksPath=/dev/null','commit','-qm','Input snapshot'],check=True)
|
|
1465
|
+
actual = subprocess.check_output(['git', '-C', str(repo), 'rev-parse', 'HEAD'], text=True).strip()
|
|
1466
|
+
if not packet.get('generated_workspace') and actual != packet['base_commit']:
|
|
1467
|
+
raise ValueError('task image base differs: ' + actual)
|
|
1468
|
+
git = ['git', '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-C', str(repo)]
|
|
1469
|
+
if not packet.get('generated_workspace'):
|
|
1470
|
+
ancestors = set(subprocess.check_output([*git, 'rev-list', packet['base_commit']], text=True).splitlines())
|
|
1471
|
+
objects = subprocess.check_output([*git, 'cat-file', '--batch-all-objects', '--batch-check=%(objectname) %(objecttype)'], text=True).splitlines()
|
|
1472
|
+
commits = {line.split()[0] for line in objects if line.endswith(' commit')}
|
|
1473
|
+
if not commits <= ancestors:
|
|
1474
|
+
raise ValueError('task image contains later Git commits')
|
|
1475
|
+
subprocess.run([*git, 'add', '-A', '--', '.'], check=True)
|
|
1476
|
+
tree = subprocess.check_output([*git, 'write-tree'], text=True).strip()
|
|
1477
|
+
pathlib.Path('/evolution-control/base-tree.txt').write_text(tree)
|
|
1478
|
+
subprocess.run(['chown', '-R', '10000:10000', str(repo)], check=True)
|
|
1479
|
+
if pathlib.Path('/go').is_dir():
|
|
1480
|
+
subprocess.run(['chown', '-R', '10000:10000', '/go'], check=True)
|
|
1481
|
+
if packet.get('grading'):
|
|
1482
|
+
logs = pathlib.Path('/logs/verifier')
|
|
1483
|
+
logs.mkdir(parents=True, exist_ok=True)
|
|
1484
|
+
os.chown(logs, 10000, 10000)
|
|
1485
|
+
files = {}
|
|
1486
|
+
for name in packet.get('remove_paths', []):
|
|
1487
|
+
if pathlib.Path(name).exists() or pathlib.Path(name).is_symlink():
|
|
1488
|
+
raise ValueError('removed instruction is still present')
|
|
1489
|
+
for name, row in packet['files'].items():
|
|
1490
|
+
path = pathlib.Path(name)
|
|
1491
|
+
expected = base64.b64decode(row['data'])
|
|
1492
|
+
if path.is_symlink() or path.read_bytes() != expected:
|
|
1493
|
+
raise ValueError('staged file differs')
|
|
1494
|
+
files[name] = hashlib.sha256(expected).hexdigest()
|
|
1495
|
+
receipt = {'base_commit': actual, 'baseline_tree': tree, 'input_files': files, 'removed_paths': packet.get('remove_paths', []), 'uid': 10000}
|
|
1496
|
+
pathlib.Path('/evolution-control/instruction-receipt.json').write_text(json.dumps(receipt))
|
|
1497
|
+
print(json.dumps(receipt))
|
|
1498
|
+
"""
|
|
1499
|
+
prepared = container_exec(
|
|
1500
|
+
runtime,
|
|
1501
|
+
execution,
|
|
1502
|
+
["python3", "-c", basic],
|
|
1503
|
+
timeout=max(1, min(180, execution["command_deadline"] - time.time())),
|
|
1504
|
+
check=False,
|
|
1505
|
+
)
|
|
1506
|
+
if prepared.returncode:
|
|
1507
|
+
raise RemoteError(
|
|
1508
|
+
"task image or staged input did not match its frozen contract"
|
|
1509
|
+
)
|
|
1510
|
+
receipt = json.loads(prepared.stdout)
|
|
1511
|
+
receipt.update(
|
|
1512
|
+
image_id=resource["Image"],
|
|
1513
|
+
apparmor_profile=resource.get("AppArmorProfile"),
|
|
1514
|
+
mounts=[
|
|
1515
|
+
{
|
|
1516
|
+
"type": item["Type"],
|
|
1517
|
+
"destination": item["Destination"],
|
|
1518
|
+
"read_write": item["RW"],
|
|
1519
|
+
}
|
|
1520
|
+
for item in resource.get("Mounts", [])
|
|
1521
|
+
],
|
|
1522
|
+
)
|
|
1523
|
+
save_json(control / "instruction-receipt.json", receipt)
|
|
1524
|
+
container_files(
|
|
1525
|
+
runtime,
|
|
1526
|
+
execution,
|
|
1527
|
+
{"/evolution-control/instruction-receipt.json": encoded_file(encoded(receipt))},
|
|
1528
|
+
)
|
|
1529
|
+
return {"sandbox_id": execution["sandbox_id"], "engine_id": identity}
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
def inner_inspection(runtime, execution):
|
|
1533
|
+
result = container_exec(
|
|
1534
|
+
runtime,
|
|
1535
|
+
execution,
|
|
1536
|
+
["python3", "/evolution-control/remote.py", "--inspect", "/evolution-control"],
|
|
1537
|
+
check=False,
|
|
1538
|
+
)
|
|
1539
|
+
if result.returncode:
|
|
1540
|
+
raise UnresolvedError("original command receipt is unavailable")
|
|
1541
|
+
return json.loads(result.stdout)
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def mirror_capture(runtime, execution, receipt):
|
|
1545
|
+
control = host_control(execution)
|
|
1546
|
+
with tempfile.TemporaryDirectory(prefix="capture-", dir=control) as temporary:
|
|
1547
|
+
target = Path(temporary) / "artifacts"
|
|
1548
|
+
docker(
|
|
1549
|
+
runtime,
|
|
1550
|
+
[
|
|
1551
|
+
"cp",
|
|
1552
|
+
execution["sandbox_id"] + ":/evolution-control/artifacts",
|
|
1553
|
+
str(target),
|
|
1554
|
+
],
|
|
1555
|
+
)
|
|
1556
|
+
for row in receipt["artifact_manifest"]:
|
|
1557
|
+
name = Path(row["path"])
|
|
1558
|
+
if name.is_absolute() or ".." in name.parts:
|
|
1559
|
+
raise UnresolvedError("artifact manifest escapes capture directory")
|
|
1560
|
+
source = target / name
|
|
1561
|
+
raw = source.read_bytes()
|
|
1562
|
+
if len(raw) != row["size"] or digest(raw) != row["sha256"]:
|
|
1563
|
+
raise UnresolvedError("captured artifact hash differs")
|
|
1564
|
+
copy_artifact(source, control / "artifacts" / name)
|
|
1565
|
+
save_json(control / "receipt.json", receipt)
|
|
1566
|
+
if (control / "typesafe-credential.json").exists():
|
|
1567
|
+
destroy_typesafe_credential(runtime, execution, owned_resource(runtime, execution))
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
def host_supervise(control):
|
|
1571
|
+
control = Path(control)
|
|
1572
|
+
runtime = read_json(control / "runtime.json")
|
|
1573
|
+
execution = read_json(control / "receipt.json")
|
|
1574
|
+
descriptor = os.open(
|
|
1575
|
+
control / "host-launch.claim", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600
|
|
1576
|
+
)
|
|
1577
|
+
os.close(descriptor)
|
|
1578
|
+
docker(
|
|
1579
|
+
runtime,
|
|
1580
|
+
[
|
|
1581
|
+
"exec",
|
|
1582
|
+
"--detach",
|
|
1583
|
+
"--user",
|
|
1584
|
+
"root",
|
|
1585
|
+
execution["sandbox_id"],
|
|
1586
|
+
"python3",
|
|
1587
|
+
"/evolution-control/remote.py",
|
|
1588
|
+
"--supervise",
|
|
1589
|
+
"/evolution-control",
|
|
1590
|
+
],
|
|
1591
|
+
)
|
|
1592
|
+
while time.time() < execution["resource_deadline"]:
|
|
1593
|
+
try:
|
|
1594
|
+
inspected = inner_inspection(runtime, execution)
|
|
1595
|
+
receipt = inspected["receipt"]
|
|
1596
|
+
if receipt["phase"] == "captured":
|
|
1597
|
+
mirror_capture(runtime, execution, receipt)
|
|
1598
|
+
return receipt
|
|
1599
|
+
save_json(control / "receipt.json", receipt)
|
|
1600
|
+
except (RemoteError, OSError, json.JSONDecodeError):
|
|
1601
|
+
pass
|
|
1602
|
+
time.sleep(0.5)
|
|
1603
|
+
raise UnresolvedError(
|
|
1604
|
+
"capture window expired; preserve the registered resource and receipts"
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
|
|
1608
|
+
def dispatch_request(request):
|
|
1609
|
+
runtime, execution, payload = (
|
|
1610
|
+
request["runtime"],
|
|
1611
|
+
request["execution"],
|
|
1612
|
+
request.get("payload"),
|
|
1613
|
+
)
|
|
1614
|
+
if (runtime["ssh_target"], runtime["remote_root"]) != (
|
|
1615
|
+
execution["ssh_target"],
|
|
1616
|
+
execution["remote_root"],
|
|
1617
|
+
):
|
|
1618
|
+
raise UnresolvedError("SSH execution scope differs")
|
|
1619
|
+
if digest(Path(__file__).read_bytes()) != execution["runner_hash"]:
|
|
1620
|
+
raise UnresolvedError("deployed runner differs from the frozen source")
|
|
1621
|
+
operation = request["operation"]
|
|
1622
|
+
if operation == "create":
|
|
1623
|
+
return create_resource(runtime, execution, payload)
|
|
1624
|
+
control = host_control(execution)
|
|
1625
|
+
original = read_json(control / "receipt.json")
|
|
1626
|
+
for key in ["remote_task_id", "input_hash", "command_hash", "runner_hash"]:
|
|
1627
|
+
if original[key] != execution[key]:
|
|
1628
|
+
raise UnresolvedError("stored execution identity differs")
|
|
1629
|
+
if operation == "read_artifact":
|
|
1630
|
+
matches = [
|
|
1631
|
+
row
|
|
1632
|
+
for row in original["artifact_manifest"]
|
|
1633
|
+
if row["path"] == payload["path"]
|
|
1634
|
+
]
|
|
1635
|
+
if original["phase"] != "captured" or len(matches) != 1:
|
|
1636
|
+
raise UnresolvedError("artifact is not in the captured manifest")
|
|
1637
|
+
return {
|
|
1638
|
+
"data": base64.b64encode(
|
|
1639
|
+
(control / "artifacts" / matches[0]["path"]).read_bytes()
|
|
1640
|
+
).decode("ascii")
|
|
1641
|
+
}
|
|
1642
|
+
resource = owned_resource(runtime, original)
|
|
1643
|
+
if operation == "destroy_typesafe":
|
|
1644
|
+
return {"destroyed": destroy_typesafe_credential(runtime, original, resource)}
|
|
1645
|
+
if operation == "prepare_typesafe":
|
|
1646
|
+
if resource is None or original["submission_status"] != "resource_created":
|
|
1647
|
+
raise UnresolvedError("credential preparation requires the original prepared resource")
|
|
1648
|
+
return prepare_typesafe_credential(runtime, original, payload)
|
|
1649
|
+
if operation == "query":
|
|
1650
|
+
if original["phase"] == "captured" and (control / "artifacts").is_dir():
|
|
1651
|
+
destroy_typesafe_credential(runtime, original, resource)
|
|
1652
|
+
return {
|
|
1653
|
+
"receipt": original,
|
|
1654
|
+
"resource_state": "missing"
|
|
1655
|
+
if resource is None
|
|
1656
|
+
else resource["State"]["Status"],
|
|
1657
|
+
"command_alive": False,
|
|
1658
|
+
"supervisor_alive": False,
|
|
1659
|
+
}
|
|
1660
|
+
if resource is None or not resource["State"]["Running"]:
|
|
1661
|
+
raise UnresolvedError(
|
|
1662
|
+
"resource ended before complete evidence was recovered"
|
|
1663
|
+
)
|
|
1664
|
+
inspected = inner_inspection(runtime, original)
|
|
1665
|
+
if inspected["receipt"]["phase"] == "captured":
|
|
1666
|
+
mirror_capture(runtime, original, inspected["receipt"])
|
|
1667
|
+
return {**inspected, "resource_state": resource["State"]["Status"]}
|
|
1668
|
+
if operation == "cleanup":
|
|
1669
|
+
if original["phase"] != "captured" and not payload.get("force_reason"):
|
|
1670
|
+
raise UnresolvedError("cleanup would discard uncaptured evidence")
|
|
1671
|
+
credentials_destroyed = destroy_typesafe_credential(runtime, original, resource)
|
|
1672
|
+
if resource is not None:
|
|
1673
|
+
docker(runtime, ["stop", "--time", "2", resource["Id"]])
|
|
1674
|
+
docker(runtime, ["rm", "--volumes", resource["Id"]])
|
|
1675
|
+
return {"terminated": owned_resource(runtime, original) is None and credentials_destroyed}
|
|
1676
|
+
if resource is None:
|
|
1677
|
+
raise UnresolvedError("registered container is unavailable")
|
|
1678
|
+
if operation == "cancel":
|
|
1679
|
+
container_files(
|
|
1680
|
+
runtime,
|
|
1681
|
+
original,
|
|
1682
|
+
{"/evolution-control/cancel.json": encoded_file(encoded(payload))},
|
|
1683
|
+
)
|
|
1684
|
+
return {"cancel_requested": True}
|
|
1685
|
+
if operation == "capture_only":
|
|
1686
|
+
captured = container_exec(
|
|
1687
|
+
runtime,
|
|
1688
|
+
original,
|
|
1689
|
+
[
|
|
1690
|
+
"python3",
|
|
1691
|
+
"/evolution-control/remote.py",
|
|
1692
|
+
"--capture-only",
|
|
1693
|
+
"/evolution-control",
|
|
1694
|
+
],
|
|
1695
|
+
check=False,
|
|
1696
|
+
)
|
|
1697
|
+
if captured.returncode:
|
|
1698
|
+
raise UnresolvedError(
|
|
1699
|
+
"original processes could not be confirmed stopped for capture-only"
|
|
1700
|
+
)
|
|
1701
|
+
receipt = json.loads(captured.stdout)
|
|
1702
|
+
mirror_capture(runtime, original, receipt)
|
|
1703
|
+
return {"receipt": receipt}
|
|
1704
|
+
if operation in {"restore_codex", "cache_codex"}:
|
|
1705
|
+
if (control / "host-launch.claim").exists():
|
|
1706
|
+
raise UnresolvedError("tooling cache cannot change after task startup")
|
|
1707
|
+
cache = (
|
|
1708
|
+
Path(runtime["remote_root"])
|
|
1709
|
+
/ "tools"
|
|
1710
|
+
/ (
|
|
1711
|
+
"codex-"
|
|
1712
|
+
+ runtime["codex_version"]
|
|
1713
|
+
+ "-"
|
|
1714
|
+
+ runtime["codex_architecture"]
|
|
1715
|
+
)
|
|
1716
|
+
)
|
|
1717
|
+
package, index = cache / "package", cache / "manifest.json"
|
|
1718
|
+
if operation == "restore_codex":
|
|
1719
|
+
if not index.exists():
|
|
1720
|
+
return {"restored": False}
|
|
1721
|
+
entries = tooling_manifest(package)
|
|
1722
|
+
if entries != read_json(index):
|
|
1723
|
+
raise UnresolvedError("cached tooling differs from its saved manifest")
|
|
1724
|
+
docker(
|
|
1725
|
+
runtime,
|
|
1726
|
+
["cp", str(package), original["sandbox_id"] + ":/opt/evolution-codex"],
|
|
1727
|
+
timeout=120,
|
|
1728
|
+
)
|
|
1729
|
+
return {"restored": True, "manifest_sha256": digest(encoded(entries))}
|
|
1730
|
+
version = (
|
|
1731
|
+
container_exec(runtime, original, ["/usr/local/bin/codex", "--version"])
|
|
1732
|
+
.stdout.decode()
|
|
1733
|
+
.strip()
|
|
1734
|
+
)
|
|
1735
|
+
if version != "codex-cli " + runtime["codex_version"]:
|
|
1736
|
+
raise RemoteError("cannot cache a different Codex version")
|
|
1737
|
+
staging = control / "installed-tooling"
|
|
1738
|
+
docker(
|
|
1739
|
+
runtime,
|
|
1740
|
+
["cp", original["sandbox_id"] + ":/opt/evolution-codex", str(staging)],
|
|
1741
|
+
timeout=120,
|
|
1742
|
+
)
|
|
1743
|
+
entries = tooling_manifest(staging)
|
|
1744
|
+
cache.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
1745
|
+
if index.exists():
|
|
1746
|
+
if read_json(index) != entries or tooling_manifest(package) != entries:
|
|
1747
|
+
raise UnresolvedError(
|
|
1748
|
+
"installed tooling differs from the existing cache"
|
|
1749
|
+
)
|
|
1750
|
+
else:
|
|
1751
|
+
staging.rename(package)
|
|
1752
|
+
save_json(index, entries)
|
|
1753
|
+
return {"cached": True, "manifest_sha256": digest(encoded(entries))}
|
|
1754
|
+
if operation == "setup_exec":
|
|
1755
|
+
if (control / "host-launch.claim").exists():
|
|
1756
|
+
raise UnresolvedError(
|
|
1757
|
+
"setup cannot execute after the original command has started"
|
|
1758
|
+
)
|
|
1759
|
+
result = container_exec(
|
|
1760
|
+
runtime,
|
|
1761
|
+
original,
|
|
1762
|
+
["bash", "-c", payload["command"]],
|
|
1763
|
+
user=payload.get("user") or "root",
|
|
1764
|
+
cwd=payload.get("cwd"),
|
|
1765
|
+
env=payload.get("env"),
|
|
1766
|
+
timeout=payload.get("timeout_sec") or 300,
|
|
1767
|
+
check=False,
|
|
1768
|
+
)
|
|
1769
|
+
observation = {
|
|
1770
|
+
"return_code": result.returncode,
|
|
1771
|
+
"stdout": result.stdout.decode(errors="replace"),
|
|
1772
|
+
"stderr": result.stderr.decode(errors="replace"),
|
|
1773
|
+
}
|
|
1774
|
+
with (control / "setup.jsonl").open("ab") as stream:
|
|
1775
|
+
stream.write(encoded(observation) + b"\n")
|
|
1776
|
+
return observation
|
|
1777
|
+
if operation == "setup_files":
|
|
1778
|
+
if (control / "host-launch.claim").exists():
|
|
1779
|
+
raise UnresolvedError("setup cannot alter a started execution")
|
|
1780
|
+
container_files(runtime, original, payload)
|
|
1781
|
+
return {"staged": True}
|
|
1782
|
+
if operation == "finalize_setup":
|
|
1783
|
+
if (control / "host-launch.claim").exists():
|
|
1784
|
+
raise UnresolvedError("setup cannot alter a started execution")
|
|
1785
|
+
packet = read_json(control / "payload.json")
|
|
1786
|
+
authentication = runtime.get("authentication")
|
|
1787
|
+
if authentication is not None:
|
|
1788
|
+
authentication_bytes = Path(authentication["path"]).read_bytes()
|
|
1789
|
+
container_files(
|
|
1790
|
+
runtime,
|
|
1791
|
+
original,
|
|
1792
|
+
{
|
|
1793
|
+
"/home/evolution/.codex/auth.json": encoded_file(
|
|
1794
|
+
authentication_bytes, 0o600
|
|
1795
|
+
),
|
|
1796
|
+
"/evolution-control/redaction.json": encoded_file(
|
|
1797
|
+
encoded(credential_values(json.loads(authentication_bytes))),
|
|
1798
|
+
0o600,
|
|
1799
|
+
),
|
|
1800
|
+
},
|
|
1801
|
+
)
|
|
1802
|
+
setup = (
|
|
1803
|
+
"import base64,hashlib,json,os,pathlib,subprocess,sys; "
|
|
1804
|
+
"sys.path.insert(0,'/evolution-control'); from remote import instruction_inventory; "
|
|
1805
|
+
"packet=json.loads(pathlib.Path('/evolution-control/payload.json').read_text()); "
|
|
1806
|
+
"home=pathlib.Path('/home/evolution'); "
|
|
1807
|
+
"[(os.chown(p,10000,10000)) for p in [home,*home.rglob('*')] if not p.is_symlink()]; "
|
|
1808
|
+
"binary=pathlib.Path('/usr/local/bin/codex').resolve(); "
|
|
1809
|
+
"candidates=[binary]+list(binary.parent.parent.parent.rglob('codex')); "
|
|
1810
|
+
"native=next(p for p in candidates if p.is_file() and p.open('rb').read(4)==b'\\x7fELF'); "
|
|
1811
|
+
"receipt=json.loads(pathlib.Path('/evolution-control/instruction-receipt.json').read_text()); "
|
|
1812
|
+
"inventory=instruction_inventory(pathlib.Path(packet['repo_path']),home,{name:{'sha256':hashlib.sha256(base64.b64decode(row['data'])).hexdigest()} for name,row in packet['files'].items()}); "
|
|
1813
|
+
"receipt.update({'instruction_sources':inventory['sources'],'source_gaps':inventory['gaps']}); "
|
|
1814
|
+
"receipt.update({'codex_version':subprocess.check_output(['/usr/local/bin/codex','--version'],text=True).strip(),"
|
|
1815
|
+
"'binary_sha256':hashlib.sha256(native.read_bytes()).hexdigest(),"
|
|
1816
|
+
"'launcher_sha256':hashlib.sha256(binary.read_bytes()).hexdigest(),"
|
|
1817
|
+
"'config_sha256':hashlib.sha256(pathlib.Path('/home/evolution/.codex/config.toml').read_bytes()).hexdigest()}); "
|
|
1818
|
+
"pathlib.Path('/evolution-control/instruction-receipt.json').write_text(json.dumps(receipt)); "
|
|
1819
|
+
"assert not inventory['gaps'], 'undeclared instruction sources'; "
|
|
1820
|
+
"print(json.dumps(receipt))"
|
|
1821
|
+
)
|
|
1822
|
+
result = container_exec(runtime, original, ["python3", "-c", setup])
|
|
1823
|
+
receipt = json.loads(result.stdout)
|
|
1824
|
+
if receipt["codex_version"] != "codex-cli " + runtime["codex_version"]:
|
|
1825
|
+
raise RemoteError("installed Codex version differs from the frozen runtime")
|
|
1826
|
+
if any(
|
|
1827
|
+
receipt.get(name) != expected
|
|
1828
|
+
for name, expected in packet.get("expected_runtime", {}).items()
|
|
1829
|
+
):
|
|
1830
|
+
raise RemoteError("actual Codex runtime differs from the calibrated binary")
|
|
1831
|
+
expected_config = packet["codex_config"]
|
|
1832
|
+
receipt["configuration"] = expected_config
|
|
1833
|
+
save_json(control / "instruction-receipt.json", receipt)
|
|
1834
|
+
container_files(
|
|
1835
|
+
runtime,
|
|
1836
|
+
original,
|
|
1837
|
+
{
|
|
1838
|
+
"/evolution-control/instruction-receipt.json": encoded_file(
|
|
1839
|
+
encoded(receipt)
|
|
1840
|
+
)
|
|
1841
|
+
},
|
|
1842
|
+
)
|
|
1843
|
+
return receipt
|
|
1844
|
+
if operation == "launch":
|
|
1845
|
+
if (control / "host-launch.claim").exists():
|
|
1846
|
+
return {"launched": True}
|
|
1847
|
+
with (control / "host-runner.log").open("ab") as output:
|
|
1848
|
+
process = subprocess.Popen(
|
|
1849
|
+
[
|
|
1850
|
+
sys.executable,
|
|
1851
|
+
str(control / "remote.py"),
|
|
1852
|
+
"--host-supervise",
|
|
1853
|
+
str(control),
|
|
1854
|
+
],
|
|
1855
|
+
stdin=subprocess.DEVNULL,
|
|
1856
|
+
stdout=output,
|
|
1857
|
+
stderr=output,
|
|
1858
|
+
start_new_session=True,
|
|
1859
|
+
env=host_environment(runtime),
|
|
1860
|
+
)
|
|
1861
|
+
save_json(
|
|
1862
|
+
control / "host-supervisor.json",
|
|
1863
|
+
{
|
|
1864
|
+
"pid": process.pid,
|
|
1865
|
+
"pgid": process.pid,
|
|
1866
|
+
"start_identity": process_identity(process.pid),
|
|
1867
|
+
},
|
|
1868
|
+
)
|
|
1869
|
+
return {"launched": True}
|
|
1870
|
+
raise RemoteError("unknown remote operation")
|
|
1871
|
+
|
|
1872
|
+
|
|
1873
|
+
def process_identity(pid):
|
|
1874
|
+
try:
|
|
1875
|
+
proc = Path("/proc") / str(pid)
|
|
1876
|
+
if proc.is_dir():
|
|
1877
|
+
# Emulated /proc/stat start ticks can differ by one tick between
|
|
1878
|
+
# self and external readers. The task directory inode is stable
|
|
1879
|
+
# for that process lifetime and changes when the PID is reused.
|
|
1880
|
+
return (
|
|
1881
|
+
Path("/proc/sys/kernel/random/boot_id").read_text().strip()
|
|
1882
|
+
+ ":"
|
|
1883
|
+
+ str(proc.stat().st_ino)
|
|
1884
|
+
)
|
|
1885
|
+
result = subprocess.run(
|
|
1886
|
+
["ps", "-o", "lstart=", "-p", str(pid)], capture_output=True, text=True
|
|
1887
|
+
)
|
|
1888
|
+
return result.stdout.strip() or None
|
|
1889
|
+
except (OSError, ValueError, IndexError):
|
|
1890
|
+
return None
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
def process_alive(process):
|
|
1894
|
+
if process is None:
|
|
1895
|
+
return False
|
|
1896
|
+
try:
|
|
1897
|
+
os.kill(process["pid"], 0)
|
|
1898
|
+
return process_identity(process["pid"]) == process["start_identity"]
|
|
1899
|
+
except (OSError, KeyError):
|
|
1900
|
+
return False
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
def inspect_command(control):
|
|
1904
|
+
control = Path(control)
|
|
1905
|
+
receipt = read_json(control / "receipt.json")
|
|
1906
|
+
runner_path = control / "supervisor.json"
|
|
1907
|
+
runner = read_json(runner_path) if runner_path.exists() else None
|
|
1908
|
+
command_alive = process_alive(receipt.get("process"))
|
|
1909
|
+
supervisor_alive = process_alive(runner)
|
|
1910
|
+
latest = read_json(control / "receipt.json")
|
|
1911
|
+
if latest != receipt:
|
|
1912
|
+
receipt = latest
|
|
1913
|
+
command_alive = process_alive(receipt.get("process"))
|
|
1914
|
+
return {
|
|
1915
|
+
"receipt": receipt,
|
|
1916
|
+
"command_alive": command_alive,
|
|
1917
|
+
"supervisor_alive": supervisor_alive,
|
|
1918
|
+
"claimed": (control / "launch.claim").exists(),
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
|
|
1922
|
+
def process_options(command):
|
|
1923
|
+
options = {"cwd": command["cwd"], "env": command["env"], "start_new_session": True}
|
|
1924
|
+
if command["uid"] is not None:
|
|
1925
|
+
options.update(user=command["uid"], group=command["gid"], extra_groups=[])
|
|
1926
|
+
return options
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
def task_pids(uid):
|
|
1930
|
+
# The production supervisor is root inside its own container. Never scan
|
|
1931
|
+
# or signal the developer's user during deterministic process fixtures.
|
|
1932
|
+
if uid is None or uid < 10000 or os.geteuid() != 0 or not Path("/proc").is_dir():
|
|
1933
|
+
return []
|
|
1934
|
+
result = []
|
|
1935
|
+
for entry in Path("/proc").iterdir():
|
|
1936
|
+
if not entry.name.isdigit():
|
|
1937
|
+
continue
|
|
1938
|
+
try:
|
|
1939
|
+
for line in (entry / "status").read_text().splitlines():
|
|
1940
|
+
if line.startswith("Uid:") and int(line.split()[1]) == uid:
|
|
1941
|
+
result.append(int(entry.name))
|
|
1942
|
+
except (OSError, ValueError):
|
|
1943
|
+
continue
|
|
1944
|
+
return result
|
|
1945
|
+
|
|
1946
|
+
|
|
1947
|
+
def stop_processes(process, uid, child=None, grace=0.5):
|
|
1948
|
+
for sig in [signal.SIGTERM, signal.SIGKILL]:
|
|
1949
|
+
if process is not None:
|
|
1950
|
+
try:
|
|
1951
|
+
os.killpg(process["pgid"], sig)
|
|
1952
|
+
except ProcessLookupError:
|
|
1953
|
+
pass
|
|
1954
|
+
for pid in task_pids(uid):
|
|
1955
|
+
try:
|
|
1956
|
+
os.kill(pid, sig)
|
|
1957
|
+
except ProcessLookupError:
|
|
1958
|
+
pass
|
|
1959
|
+
if sig == signal.SIGTERM:
|
|
1960
|
+
time.sleep(grace)
|
|
1961
|
+
if child is not None:
|
|
1962
|
+
# Reap a terminated leader before signalling remaining group
|
|
1963
|
+
# members; macOS rejects killpg against a zombie-only group.
|
|
1964
|
+
child.poll()
|
|
1965
|
+
|
|
1966
|
+
|
|
1967
|
+
def copy_artifact(source, destination):
|
|
1968
|
+
source = Path(source)
|
|
1969
|
+
path = source
|
|
1970
|
+
while path != path.parent:
|
|
1971
|
+
if path.is_symlink():
|
|
1972
|
+
raise ValueError("artifact source cannot be a symlink")
|
|
1973
|
+
path = path.parent
|
|
1974
|
+
descriptor = os.open(source, os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW)
|
|
1975
|
+
with os.fdopen(descriptor, "rb") as stream:
|
|
1976
|
+
if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
|
|
1977
|
+
raise ValueError("artifact source must be a regular file")
|
|
1978
|
+
save_bytes(destination, stream.read())
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
def capture_artifacts(control, command):
|
|
1982
|
+
artifacts = control / "artifacts"
|
|
1983
|
+
artifacts.mkdir(mode=0o700, exist_ok=True)
|
|
1984
|
+
gaps = []
|
|
1985
|
+
capture = command["capture"]
|
|
1986
|
+
repository = capture["repo"]
|
|
1987
|
+
if repository is not None:
|
|
1988
|
+
env = {
|
|
1989
|
+
**command["env"],
|
|
1990
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
1991
|
+
"GIT_CONFIG_GLOBAL": "/dev/null",
|
|
1992
|
+
}
|
|
1993
|
+
options = {**process_options(command), "cwd": repository, "env": env}
|
|
1994
|
+
git = [
|
|
1995
|
+
"git",
|
|
1996
|
+
"-c",
|
|
1997
|
+
"core.hooksPath=/dev/null",
|
|
1998
|
+
"-c",
|
|
1999
|
+
"core.fsmonitor=false",
|
|
2000
|
+
"-c",
|
|
2001
|
+
"core.pager=cat",
|
|
2002
|
+
]
|
|
2003
|
+
added = subprocess.run(
|
|
2004
|
+
[*git, "add", "-A", "--", "."], capture_output=True, timeout=30, **options
|
|
2005
|
+
)
|
|
2006
|
+
baseline = (
|
|
2007
|
+
Path(capture["baseline_ref_file"]).read_text().strip()
|
|
2008
|
+
if capture.get("baseline_ref_file")
|
|
2009
|
+
else "HEAD"
|
|
2010
|
+
)
|
|
2011
|
+
patch = subprocess.run(
|
|
2012
|
+
[
|
|
2013
|
+
*git,
|
|
2014
|
+
"diff",
|
|
2015
|
+
"--cached",
|
|
2016
|
+
"--binary",
|
|
2017
|
+
"--no-ext-diff",
|
|
2018
|
+
"--no-textconv",
|
|
2019
|
+
baseline,
|
|
2020
|
+
"--",
|
|
2021
|
+
".",
|
|
2022
|
+
*[":(exclude)" + name for name in capture["excluded_paths"]],
|
|
2023
|
+
],
|
|
2024
|
+
capture_output=True,
|
|
2025
|
+
timeout=30,
|
|
2026
|
+
**options,
|
|
2027
|
+
)
|
|
2028
|
+
if added.returncode or patch.returncode:
|
|
2029
|
+
gaps.append("patch_capture_failed")
|
|
2030
|
+
save_json(
|
|
2031
|
+
artifacts / "patch-capture.json",
|
|
2032
|
+
{
|
|
2033
|
+
"add_exit_code": added.returncode,
|
|
2034
|
+
"diff_exit_code": patch.returncode,
|
|
2035
|
+
"add_stderr": added.stderr.decode(errors="replace"),
|
|
2036
|
+
"diff_stderr": patch.stderr.decode(errors="replace"),
|
|
2037
|
+
},
|
|
2038
|
+
)
|
|
2039
|
+
else:
|
|
2040
|
+
save_bytes(artifacts / "model.patch", patch.stdout)
|
|
2041
|
+
session = capture["session_dir"]
|
|
2042
|
+
runtime_files, runtime_names = {}, []
|
|
2043
|
+
if session is not None:
|
|
2044
|
+
root = Path(session)
|
|
2045
|
+
if root.is_dir() and not root.is_symlink():
|
|
2046
|
+
for source in sorted(root.rglob("*")):
|
|
2047
|
+
if source.is_dir() and not source.is_symlink():
|
|
2048
|
+
continue
|
|
2049
|
+
try:
|
|
2050
|
+
copy_artifact(
|
|
2051
|
+
source, artifacts / "sessions" / source.relative_to(root)
|
|
2052
|
+
)
|
|
2053
|
+
except (OSError, ValueError):
|
|
2054
|
+
gaps.append("session_capture_failed")
|
|
2055
|
+
if capture.get("runtime_skills"):
|
|
2056
|
+
builtins = root.parent / "skills/.system"
|
|
2057
|
+
for entry in sorted(builtins.glob("*/SKILL.md")):
|
|
2058
|
+
runtime_names.append(entry.parent.name)
|
|
2059
|
+
for source in sorted(entry.parent.rglob("*")):
|
|
2060
|
+
if source.is_dir() and not source.is_symlink():
|
|
2061
|
+
continue
|
|
2062
|
+
if "__pycache__" in source.parts or source.suffix == ".pyc":
|
|
2063
|
+
continue
|
|
2064
|
+
name = source.relative_to(builtins).as_posix()
|
|
2065
|
+
try:
|
|
2066
|
+
copy_artifact(source, artifacts / "runtime-skills" / name)
|
|
2067
|
+
runtime_files[name] = stat.S_IMODE(source.stat().st_mode)
|
|
2068
|
+
except (OSError, ValueError):
|
|
2069
|
+
gaps.append("runtime_skill_capture_failed")
|
|
2070
|
+
if not runtime_names:
|
|
2071
|
+
gaps.append("runtime_skills_unavailable")
|
|
2072
|
+
for name, source in capture["files"].items():
|
|
2073
|
+
path = Path(name)
|
|
2074
|
+
if path.is_absolute() or ".." in path.parts:
|
|
2075
|
+
raise ValueError("artifact name must stay relative")
|
|
2076
|
+
try:
|
|
2077
|
+
copy_artifact(source, artifacts / path)
|
|
2078
|
+
except (OSError, ValueError):
|
|
2079
|
+
gaps.append("missing_artifact:" + name)
|
|
2080
|
+
for name, source in capture.get("optional_files", {}).items():
|
|
2081
|
+
path = Path(name)
|
|
2082
|
+
if path.is_absolute() or ".." in path.parts:
|
|
2083
|
+
raise ValueError("optional artifact name must stay relative")
|
|
2084
|
+
if Path(source).exists():
|
|
2085
|
+
copy_artifact(source, artifacts / path)
|
|
2086
|
+
secrets = (
|
|
2087
|
+
read_json(control / "redaction.json")
|
|
2088
|
+
if (control / "redaction.json").exists()
|
|
2089
|
+
else []
|
|
2090
|
+
)
|
|
2091
|
+
auth_path = Path(session).parent / "auth.json" if session is not None else None
|
|
2092
|
+
if auth_path is not None and auth_path.is_file():
|
|
2093
|
+
secrets.extend(credential_values(read_json(auth_path)))
|
|
2094
|
+
redacted = []
|
|
2095
|
+
for path in sorted(artifacts.rglob("*")):
|
|
2096
|
+
if not path.is_file():
|
|
2097
|
+
continue
|
|
2098
|
+
raw = path.read_bytes()
|
|
2099
|
+
changed = raw
|
|
2100
|
+
for secret in set(secrets):
|
|
2101
|
+
changed = changed.replace(secret.encode(), b"[REDACTED]")
|
|
2102
|
+
if changed != raw:
|
|
2103
|
+
if path.name.endswith(".patch"):
|
|
2104
|
+
path.unlink()
|
|
2105
|
+
gaps.append("credential_in_patch")
|
|
2106
|
+
else:
|
|
2107
|
+
save_bytes(path, changed)
|
|
2108
|
+
redacted.append(path.relative_to(artifacts).as_posix())
|
|
2109
|
+
if auth_path is not None and auth_path.exists():
|
|
2110
|
+
auth_path.unlink()
|
|
2111
|
+
(control / "redaction.json").unlink(missing_ok=True)
|
|
2112
|
+
if command.get("typesafe_credential"):
|
|
2113
|
+
credential = Path(command["typesafe_credential"])
|
|
2114
|
+
credential.unlink(missing_ok=True)
|
|
2115
|
+
save_json(artifacts / "credential-receipt.json", {"provider": "typesafe", "destroyed": not credential.exists()})
|
|
2116
|
+
if capture.get("runtime_skills"):
|
|
2117
|
+
save_json(
|
|
2118
|
+
artifacts / "runtime-skills.json",
|
|
2119
|
+
{
|
|
2120
|
+
"skills": runtime_names,
|
|
2121
|
+
"files": [
|
|
2122
|
+
{
|
|
2123
|
+
"path": name,
|
|
2124
|
+
"mode": mode,
|
|
2125
|
+
"sha256": digest(
|
|
2126
|
+
(artifacts / "runtime-skills" / name).read_bytes()
|
|
2127
|
+
),
|
|
2128
|
+
}
|
|
2129
|
+
for name, mode in sorted(runtime_files.items())
|
|
2130
|
+
],
|
|
2131
|
+
},
|
|
2132
|
+
)
|
|
2133
|
+
save_json(
|
|
2134
|
+
artifacts / "capture.json",
|
|
2135
|
+
{"gaps": sorted(set(gaps)), "redacted_artifacts": redacted},
|
|
2136
|
+
)
|
|
2137
|
+
manifest = []
|
|
2138
|
+
for path in sorted(artifacts.rglob("*")):
|
|
2139
|
+
if path.is_dir():
|
|
2140
|
+
continue
|
|
2141
|
+
raw = path.read_bytes()
|
|
2142
|
+
manifest.append(
|
|
2143
|
+
{
|
|
2144
|
+
"path": path.relative_to(artifacts).as_posix(),
|
|
2145
|
+
"size": len(raw),
|
|
2146
|
+
"sha256": digest(raw),
|
|
2147
|
+
}
|
|
2148
|
+
)
|
|
2149
|
+
return manifest
|
|
2150
|
+
|
|
2151
|
+
|
|
2152
|
+
def verify_supervisor_inputs(control):
|
|
2153
|
+
receipt = read_json(control / "receipt.json")
|
|
2154
|
+
command = read_json(control / "command.json")
|
|
2155
|
+
if digest(encoded(command)) != receipt["command_hash"]:
|
|
2156
|
+
raise ValueError("frozen command hash mismatch")
|
|
2157
|
+
if digest(Path(__file__).read_bytes()) != receipt["runner_hash"]:
|
|
2158
|
+
raise ValueError("frozen supervisor hash mismatch")
|
|
2159
|
+
return receipt, command
|
|
2160
|
+
|
|
2161
|
+
|
|
2162
|
+
def run_supervisor(control):
|
|
2163
|
+
control = Path(control)
|
|
2164
|
+
receipt, command = verify_supervisor_inputs(control)
|
|
2165
|
+
try:
|
|
2166
|
+
descriptor = os.open(
|
|
2167
|
+
control / "launch.claim", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600
|
|
2168
|
+
)
|
|
2169
|
+
except FileExistsError:
|
|
2170
|
+
return read_json(control / "receipt.json")
|
|
2171
|
+
os.close(descriptor)
|
|
2172
|
+
save_json(
|
|
2173
|
+
control / "supervisor.json",
|
|
2174
|
+
{
|
|
2175
|
+
"pid": os.getpid(),
|
|
2176
|
+
"pgid": os.getpgrp(),
|
|
2177
|
+
"start_identity": process_identity(os.getpid()),
|
|
2178
|
+
},
|
|
2179
|
+
)
|
|
2180
|
+
receipt["phase"] = "starting"
|
|
2181
|
+
save_json(control / "receipt.json", receipt)
|
|
2182
|
+
artifacts = control / "artifacts"
|
|
2183
|
+
artifacts.mkdir(mode=0o700, exist_ok=True)
|
|
2184
|
+
child = None
|
|
2185
|
+
started = None
|
|
2186
|
+
try:
|
|
2187
|
+
with (
|
|
2188
|
+
(artifacts / "stdout.jsonl").open("wb") as stdout,
|
|
2189
|
+
(artifacts / "stderr.txt").open("wb") as stderr,
|
|
2190
|
+
):
|
|
2191
|
+
if time.time() >= receipt["command_deadline"]:
|
|
2192
|
+
receipt.update(
|
|
2193
|
+
phase="capturing", termination_reason="not_started_deadline"
|
|
2194
|
+
)
|
|
2195
|
+
save_json(control / "receipt.json", receipt)
|
|
2196
|
+
receipt["artifact_manifest"] = capture_artifacts(control, command)
|
|
2197
|
+
receipt["phase"] = "captured"
|
|
2198
|
+
save_json(control / "receipt.json", receipt)
|
|
2199
|
+
return receipt
|
|
2200
|
+
started = time.monotonic()
|
|
2201
|
+
child = subprocess.Popen(
|
|
2202
|
+
command["args"],
|
|
2203
|
+
stdin=subprocess.DEVNULL,
|
|
2204
|
+
stdout=stdout,
|
|
2205
|
+
stderr=stderr,
|
|
2206
|
+
**process_options(command),
|
|
2207
|
+
)
|
|
2208
|
+
identity = process_identity(child.pid)
|
|
2209
|
+
receipt["process"] = {
|
|
2210
|
+
"pid": child.pid,
|
|
2211
|
+
"pgid": child.pid,
|
|
2212
|
+
"start_identity": identity or "already_exited",
|
|
2213
|
+
}
|
|
2214
|
+
receipt["phase"] = "running"
|
|
2215
|
+
save_json(control / "receipt.json", receipt)
|
|
2216
|
+
reason = "exited"
|
|
2217
|
+
while child.poll() is None:
|
|
2218
|
+
if (control / "cancel.json").exists():
|
|
2219
|
+
reason = "cancelled"
|
|
2220
|
+
break
|
|
2221
|
+
if time.time() >= receipt["command_deadline"]:
|
|
2222
|
+
reason = "timeout"
|
|
2223
|
+
break
|
|
2224
|
+
time.sleep(0.05)
|
|
2225
|
+
stop_processes(receipt["process"], command["uid"], child)
|
|
2226
|
+
receipt["exit_code"] = child.wait(timeout=5)
|
|
2227
|
+
receipt["termination_reason"] = reason
|
|
2228
|
+
except OSError:
|
|
2229
|
+
if child is not None:
|
|
2230
|
+
stop_processes(receipt["process"], command["uid"], child)
|
|
2231
|
+
receipt["exit_code"] = child.wait(timeout=5)
|
|
2232
|
+
receipt["termination_reason"] = "launch_failed"
|
|
2233
|
+
receipt["phase"] = "capturing"
|
|
2234
|
+
save_json(control / "receipt.json", receipt)
|
|
2235
|
+
if started is not None and child is not None and receipt["exit_code"] is not None:
|
|
2236
|
+
save_json(
|
|
2237
|
+
artifacts / "command-metrics.json",
|
|
2238
|
+
{"duration_sec": time.monotonic() - started},
|
|
2239
|
+
)
|
|
2240
|
+
receipt["artifact_manifest"] = capture_artifacts(control, command)
|
|
2241
|
+
receipt["phase"] = "captured"
|
|
2242
|
+
save_json(control / "receipt.json", receipt)
|
|
2243
|
+
return receipt
|
|
2244
|
+
|
|
2245
|
+
|
|
2246
|
+
def capture_only(control):
|
|
2247
|
+
control = Path(control)
|
|
2248
|
+
receipt, command = verify_supervisor_inputs(control)
|
|
2249
|
+
if receipt["phase"] == "captured":
|
|
2250
|
+
return receipt
|
|
2251
|
+
inspection = inspect_command(control)
|
|
2252
|
+
if (
|
|
2253
|
+
inspection["command_alive"]
|
|
2254
|
+
or inspection["supervisor_alive"]
|
|
2255
|
+
or task_pids(command["uid"])
|
|
2256
|
+
):
|
|
2257
|
+
raise ValueError("cannot capture while original execution may still be running")
|
|
2258
|
+
receipt["phase"] = "capturing"
|
|
2259
|
+
if receipt["exit_code"] is None:
|
|
2260
|
+
receipt["termination_reason"] = "original_exit_unknown"
|
|
2261
|
+
save_json(control / "receipt.json", receipt)
|
|
2262
|
+
receipt["artifact_manifest"] = capture_artifacts(control, command)
|
|
2263
|
+
receipt["phase"] = "captured"
|
|
2264
|
+
save_json(control / "receipt.json", receipt)
|
|
2265
|
+
return receipt
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
def main():
|
|
2269
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
2270
|
+
entry = parser.add_mutually_exclusive_group(required=True)
|
|
2271
|
+
entry.add_argument("--supervise", type=Path)
|
|
2272
|
+
entry.add_argument("--inspect", type=Path)
|
|
2273
|
+
entry.add_argument("--capture-only", type=Path)
|
|
2274
|
+
entry.add_argument("--host-supervise", type=Path)
|
|
2275
|
+
entry.add_argument("--dispatch", action="store_true")
|
|
2276
|
+
args = parser.parse_args()
|
|
2277
|
+
try:
|
|
2278
|
+
if args.dispatch:
|
|
2279
|
+
result = dispatch_request(json.load(sys.stdin))
|
|
2280
|
+
elif args.host_supervise:
|
|
2281
|
+
result = host_supervise(args.host_supervise)
|
|
2282
|
+
elif args.supervise:
|
|
2283
|
+
result = run_supervisor(args.supervise)
|
|
2284
|
+
elif args.capture_only:
|
|
2285
|
+
result = capture_only(args.capture_only)
|
|
2286
|
+
else:
|
|
2287
|
+
result = inspect_command(args.inspect)
|
|
2288
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
2289
|
+
return 0
|
|
2290
|
+
except Exception as error:
|
|
2291
|
+
message = (
|
|
2292
|
+
str(error) if isinstance(error, RemoteError) else "remote operation failed"
|
|
2293
|
+
)
|
|
2294
|
+
print(
|
|
2295
|
+
json.dumps(
|
|
2296
|
+
{"error": message, "unresolved": isinstance(error, UnresolvedError)}
|
|
2297
|
+
)
|
|
2298
|
+
)
|
|
2299
|
+
return 3 if isinstance(error, UnresolvedError) else 4
|
|
2300
|
+
|
|
2301
|
+
|
|
2302
|
+
if __name__ == "__main__":
|
|
2303
|
+
raise SystemExit(main())
|