diff --git a/package-lock.json b/package-lock.json index 663eb65..6e1cbd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,20 +42,6 @@ } } }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -131,17 +117,17 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.20.tgz", - "integrity": "sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==", + "version": "3.975.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", + "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@aws-sdk/xml-builder": "^3.972.29", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.6", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@aws-sdk/xml-builder": "^3.972.34", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.2", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -150,15 +136,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.46.tgz", - "integrity": "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", + "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -166,17 +152,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.48", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.48.tgz", - "integrity": "sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==", + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", + "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/node-http-handler": "^4.7.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -184,13 +170,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", - "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -198,23 +184,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.52", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.52.tgz", - "integrity": "sha512-szg1nnebqC+Svv6Vfsdf6P/QK8x5g/ghG2CKa/1WkHifRnq0BBmDELj2Qnqk9nPsUvEu/OEcYic97CPLpKqF9g==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", + "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/credential-provider-env": "^3.972.46", - "@aws-sdk/credential-provider-http": "^3.972.48", - "@aws-sdk/credential-provider-login": "^3.972.51", - "@aws-sdk/credential-provider-process": "^3.972.46", - "@aws-sdk/credential-provider-sso": "^3.972.51", - "@aws-sdk/credential-provider-web-identity": "^3.972.51", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/credential-provider-imds": "^4.3.7", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-login": "^3.972.63", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -222,16 +208,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.51.tgz", - "integrity": "sha512-csHFsH+/VjnI40oqm1l1OqMY4B4kza36DbfcbHcgcbobgjebasqUbTU34xvwUkvtoNGGizbfyMSlMzJWUPv3dQ==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", + "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -239,21 +225,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.54.tgz", - "integrity": "sha512-vinTSQtziNHxi2nqXF+76jr2sO44q88Ind1qFFVaotNgBaC1rcWDjBug8yoE8n0ov33s21xks9WY5XDHH9SENw==", + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz", + "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.46", - "@aws-sdk/credential-provider-http": "^3.972.48", - "@aws-sdk/credential-provider-ini": "^3.972.52", - "@aws-sdk/credential-provider-process": "^3.972.46", - "@aws-sdk/credential-provider-sso": "^3.972.51", - "@aws-sdk/credential-provider-web-identity": "^3.972.51", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/credential-provider-imds": "^4.3.7", - "@smithy/types": "^4.14.3", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-ini": "^3.973.1", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -261,15 +247,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.46.tgz", - "integrity": "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", + "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -277,17 +263,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.51.tgz", - "integrity": "sha512-60qhpQcSDIKIr0AuBlmJezKX0b5nbJPCINiR49N9yJXrEI5tTRwsXVBr0IdSvvsNJyqgiINyoBd++Ed0yvggbw==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", + "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/token-providers": "3.1065.0", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/token-providers": "3.1083.0", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -295,16 +281,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1065.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1065.0.tgz", - "integrity": "sha512-qdHQntq82gMqG6Tf8xrgmhJxacaYkxW4PEeDg/ISMVJ84EWe7iD6JyCTgbyox3uNDH6vqEJ8GUiTaXCq307zVw==", + "version": "3.1083.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", + "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -312,16 +298,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.51.tgz", - "integrity": "sha512-0X5eWsUIp8ItRJeJBBrhQAPzc9AQelDetRTVTsycCAISCCzM17R4hs/vFAPeQ0o0B35sciLiqe/Pwmml909cZA==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", + "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -329,14 +315,14 @@ } }, "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.21", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.21.tgz", - "integrity": "sha512-mVC0hOmwGJmNFezZ+wM8Sqfap/LjsMavEf2Evl0YWrLAcrdZOEdjnY8nRvgakVViWJSGm2eJxLuPVHGdeV06kA==", + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.26.tgz", + "integrity": "sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -344,14 +330,14 @@ } }, "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.17.tgz", - "integrity": "sha512-tdbnXbw73ww62ABWP0G0Z/euvFowEEvAoi/zG4NaZo7HJFpfGho/Z65HyVzkJLT1cMsUregr4pTyxljlarT0wA==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.22.tgz", + "integrity": "sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -359,17 +345,17 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.28.tgz", - "integrity": "sha512-SCW06Zjugn86pq7+dxGnFcyWJuEWHT753HTU/Vj/OzVxP+NoShwdAr4ynxAcvWL883OgRVbSqW3ohnjIxwXjjw==", + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.39.tgz", + "integrity": "sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -377,20 +363,18 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.19.tgz", - "integrity": "sha512-P2Otgf15GBJMKzG6j5Ddf7w+Kz6z2jvesMy874TD3jlMfDWNK7clJeUd7hgigdeVOotjoUP4emcTWVdS9sfZDw==", + "version": "3.997.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", + "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/signature-v4-multi-region": "^3.996.33", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/node-http-handler": "^4.7.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -398,13 +382,13 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", - "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -412,14 +396,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.33", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.33.tgz", - "integrity": "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug==", + "version": "3.996.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", + "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -444,12 +428,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.12.tgz", - "integrity": "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", + "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -457,9 +441,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.7.tgz", - "integrity": "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==", + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -469,13 +453,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.29.tgz", - "integrity": "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==", + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", + "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.3", - "fast-xml-parser": "5.7.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -483,9 +466,9 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -569,12 +552,12 @@ } }, "node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.3.tgz", - "integrity": "sha512-3qw0/GeRQBU/nlGjDe5Yb7ePKTmoxefx2YxyKMFAviFUMXpFexBG/hS7mBtwFahFvzrrTPPoRT6sFIDjwoDWPQ==", + "version": "0.80.7-anchor.3d8f743", + "resolved": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz", + "integrity": "sha512-f8sqaHU4pnZzO3n4J+SN/ihA6RwfkHh6fTlDhMrEEPtHo/qHjz4RbnjF5nBIm/6Y1HAtGgs1N1a36VfiGsTYng==", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.80.3", + "@earendil-works/pi-ai": "0.80.7-anchor.3d8f743", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" @@ -584,9 +567,9 @@ } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.3.tgz", - "integrity": "sha512-jPZLMeGL5kkMSEAwAklfXTMHqZvfhsJtCCpKGIr5Duk7mc0n4skjB1dugk7y0z3z8ZHIUCmPAWHdyDqgUz5vdA==", + "version": "0.80.7-anchor.3d8f743", + "resolved": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz", + "integrity": "sha512-mpylF8DalhPpP9XzgF6G8EO1ViG+IDQl/10ajpfaSFpQs9b1G6lXbrVng9OdzPe7u1z6tc27OaGWaD5z1g8LdA==", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", @@ -609,15 +592,14 @@ } }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.3.tgz", - "integrity": "sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==", - "hasShrinkwrap": true, + "version": "0.80.7-anchor.3d8f743", + "resolved": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz", + "integrity": "sha512-SEIEpTPtMWu49+u53ijqH54Vg0JBJB0JiBJcFZ+A3UBWphmppQLDDDxjRfkJA8csNu+9CWLbi6j/M91cjPVMNQ==", "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.3", - "@earendil-works/pi-ai": "^0.80.3", - "@earendil-works/pi-tui": "^0.80.3", + "@earendil-works/pi-agent-core": "0.80.7-anchor.3d8f743", + "@earendil-works/pi-ai": "0.80.7-anchor.3d8f743", + "@earendil-works/pi-tui": "0.80.7-anchor.3d8f743", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", @@ -644,945 +626,191 @@ "@mariozechner/clipboard": "0.3.9" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", + "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "optional": true, + "engines": { + "node": ">= 10" }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.9", + "@mariozechner/clipboard-darwin-universal": "0.3.9", + "@mariozechner/clipboard-darwin-x64": "0.3.9", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-musl": "0.3.9", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", + "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", + "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", + "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", + "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", + "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0.0" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { - "version": "3.974.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", - "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.24", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", + "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0.0" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", - "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", + "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0.0" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", - "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", + "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0.0" + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", - "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-login": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", - "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.42", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", - "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-ini": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", - "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", - "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", - "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", - "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", - "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", - "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { - "version": "3.997.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", - "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.27", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", - "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", - "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", - "license": "Apache-2.0", - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "@earendil-works/pi-ai": "^0.80.3", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", - "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.9", - "@mariozechner/clipboard-darwin-universal": "0.3.9", - "@mariozechner/clipboard-darwin-x64": "0.3.9", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-musl": "0.3.9", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", - "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", - "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", - "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", - "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", - "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", - "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", - "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", - "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", - "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", - "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "license": "Apache-2.0" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", - "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", - "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", - "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", + "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", + "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 14" + "node": ">= 10" } }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "license": "Apache-2.0" + }, "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1592,41 +820,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", @@ -1639,12 +832,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -1653,184 +840,31 @@ "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "license": "Apache-2.0", + "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=18" + "node": ">= 8" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "license": "MIT", + "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.3.1" } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { @@ -1850,32 +884,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1903,41 +911,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1953,55 +926,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { "version": "11.4.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", @@ -2011,18 +935,6 @@ "node": "20 || >=22" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2047,111 +959,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2191,64 +998,12 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", @@ -2288,36 +1043,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "license": "MIT" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", @@ -2327,21 +1052,6 @@ "node": ">=22.19.0" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2357,79 +1067,10 @@ "node": ">= 8" } }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, "node_modules/@earendil-works/pi-tui": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", - "integrity": "sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==", + "version": "0.80.7-anchor.3d8f743", + "resolved": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz", + "integrity": "sha512-400zigCSTDd+WJACQUgAyrudUJsN5UZdGo+tjCRZ85jl91QSBEHrNL6dU8A0uMtlGyCC0bqvp1yrBz1si0HCMA==", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -3448,18 +2089,6 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@nodable/entities": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", - "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, "node_modules/@onkernel/cua-agent": { "resolved": "packages/agent", "link": true @@ -3490,9 +2119,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.42.0.tgz", - "integrity": "sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -3510,46 +2139,59 @@ }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { "version": "1.0.2", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, "node_modules/@quansync/fs": { @@ -4180,13 +2822,12 @@ ] }, "node_modules/@smithy/core": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz", - "integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==", + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz", + "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4194,13 +2835,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.8.tgz", - "integrity": "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz", + "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4208,13 +2849,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.6.tgz", - "integrity": "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.5.tgz", + "integrity": "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4248,13 +2889,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.6.tgz", - "integrity": "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.4.tgz", + "integrity": "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4262,9 +2903,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.3", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz", - "integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4351,6 +2992,8 @@ }, "node_modules/@types/retry": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, "node_modules/@tzafon/lightcone": { @@ -4476,6 +3119,8 @@ }, "node_modules/agent-base": { "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -4491,18 +3136,6 @@ "node": ">=14" } }, - "node_modules/anynum": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", - "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -4533,6 +3166,8 @@ }, "node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -4551,6 +3186,8 @@ }, "node_modules/bignumber.js": { "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -4574,6 +3211,8 @@ }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, "node_modules/cac": { @@ -4615,6 +3254,8 @@ }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", "engines": { "node": ">= 12" @@ -4684,6 +3325,8 @@ }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -4770,45 +3413,10 @@ }, "node_modules/extend": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -4829,6 +3437,8 @@ }, "node_modules/fetch-blob": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", @@ -4850,6 +3460,8 @@ }, "node_modules/formdata-polyfill": { "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -4874,7 +3486,9 @@ } }, "node_modules/gaxios": { - "version": "7.1.4", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", + "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -4887,6 +3501,8 @@ }, "node_modules/gcp-metadata": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", @@ -4923,7 +3539,9 @@ } }, "node_modules/google-auth-library": { - "version": "10.6.2", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -4939,6 +3557,8 @@ }, "node_modules/google-logging-utils": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -4953,6 +3573,8 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -4964,6 +3586,8 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -5015,6 +3639,8 @@ }, "node_modules/json-bigint": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" @@ -5035,6 +3661,8 @@ }, "node_modules/jwa": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -5044,6 +3672,8 @@ }, "node_modules/jws": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { "jwa": "^2.0.1", @@ -5052,6 +3682,8 @@ }, "node_modules/long": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/loupe": { @@ -5117,6 +3749,9 @@ }, "node_modules/node-domexception": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", "funding": [ { "type": "github", @@ -5134,6 +3769,8 @@ }, "node_modules/node-fetch": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -5199,6 +3836,8 @@ }, "node_modules/p-retry": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", "dependencies": { "@types/retry": "0.12.0", @@ -5210,23 +3849,10 @@ }, "node_modules/partial-json": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", "license": "MIT" }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -5294,22 +3920,23 @@ } }, "node_modules/protobufjs": { - "version": "7.5.5", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -5344,6 +3971,8 @@ }, "node_modules/retry": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { "node": ">= 4" @@ -5490,6 +4119,8 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -5604,21 +4235,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/strnum": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", - "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "anynum": "^1.0.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -6022,6 +4638,8 @@ }, "node_modules/web-streams-polyfill": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", "engines": { "node": ">= 8" @@ -6063,21 +4681,6 @@ } } }, - "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", @@ -6114,8 +4717,8 @@ "version": "0.6.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "0.80.3", - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-agent-core": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz", + "@earendil-works/pi-ai": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" @@ -6130,7 +4733,7 @@ "version": "0.6.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-ai": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, @@ -6144,8 +4747,8 @@ "version": "0.4.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.3", - "@earendil-works/pi-tui": "0.80.3", + "@earendil-works/pi-coding-agent": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz", + "@earendil-works/pi-tui": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz", "@onkernel/cua-agent": "0.6.0", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0" diff --git a/packages/agent/package.json b/packages/agent/package.json index 6086cc3..8814824 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -40,8 +40,8 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-agent-core": "0.80.3", - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-agent-core": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz", + "@earendil-works/pi-ai": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index ce62089..f63c684 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -646,6 +646,7 @@ export class CuaAgentHarness< private requestedActiveToolNames?: string[]; private emptyResponseRecoveryAttempts = 0; private hasPendingActiveQueue = false; + private toolMutation = Promise.resolve(); constructor(options: CuaAgentHarnessOptions) { const { @@ -730,6 +731,16 @@ export class CuaAgentHarness< this.emptyResponseRecoveryAttempts += 1; } + /** Serialize tool-list changes with model switches. */ + async mutateTools(mutation: () => Promise): Promise { + const result = this.toolMutation.then(mutation); + this.toolMutation = result.then( + () => {}, + () => {}, + ); + return result; + } + /** * Mirror pi `AgentHarness.setModel()` while accepting CUA model refs. * @@ -740,16 +751,20 @@ export class CuaAgentHarness< override async setModel(model: CuaRuntimeInput): Promise { this.runtime.setModel(model); const tools = this.runtime.tools(); - try { - await super.setTools(tools, this.requestedActiveToolNames ?? tools.map((tool) => tool.name)); - } catch (err) { - // The pre-switch tools stay exposed, so restore the runtime they are - // bound to — including its still-live translator. - this.runtime.rollbackSwitch(); - throw err; - } - this.runtime.commitSwitch(); - await super.setModel(this.runtime.model); + const resolvedModel = this.runtime.model; + await this.mutateTools(async () => { + try { + await super.setTools( + tools, + this.requestedActiveToolNames ?? tools.map((tool) => tool.name), + ); + } catch (err) { + this.runtime.rollbackSwitch(); + throw err; + } + this.runtime.commitSwitch(); + }); + await super.setModel(resolvedModel); } override async setActiveTools(toolNames: string[]): Promise { diff --git a/packages/ai/package.json b/packages/ai/package.json index 49e9ce3..f2a602b 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -41,7 +41,7 @@ "test:integration": "vitest --run --config vitest.integration.config.ts" }, "dependencies": { - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-ai": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, diff --git a/packages/ai/test/dynamic-tool-payload.test.ts b/packages/ai/test/dynamic-tool-payload.test.ts new file mode 100644 index 0000000..7618c5e --- /dev/null +++ b/packages/ai/test/dynamic-tool-payload.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it, vi } from "vitest"; +import type { AgentTool, Context, Message, Model } from "@earendil-works/pi-ai"; +import { createServer } from "node:http"; +import { cuaModels, getCuaModel, resolveCuaRuntimeSpec } from "../src/index"; +import { ANTHROPIC_NATIVE_BROWSER_MESSAGES_API } from "../src/providers/anthropic/native"; + +const { openaiCreate } = vi.hoisted(() => ({ + openaiCreate: vi.fn(), +})); + +vi.mock("openai", () => ({ + default: class { + responses = { + create: (...args: unknown[]) => { + openaiCreate(...args); + return { + withResponse: async () => { + throw new Error("stop after payload capture"); + }, + }; + }, + }; + }, +})); + +const dynamicTool: AgentTool = { + name: "learned_tool", + label: "Learned tool", + description: "learned", + parameters: { type: "object", properties: {} }, + execute: async () => ({ content: [], details: {} }), +}; +const addTool: AgentTool = { ...dynamicTool, name: "add_tool", label: "Add tool" }; + +function addMessages(api: string, responseId?: string): Message[] { + return [ + { + role: "assistant", + content: [{ type: "toolCall", id: "add_1", name: "add_tool", arguments: {} }], + api, + provider: api.startsWith("openai") ? "openai" : "anthropic", + model: api.startsWith("openai") ? "gpt-5.5" : "claude-sonnet-4-5", + responseId, + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "toolUse", + timestamp: 0, + }, + { + role: "toolResult", + toolCallId: "add_1", + toolName: "add_tool", + content: [{ type: "text", text: "added" }], + addedToolNames: ["learned_tool"], + isError: false, + timestamp: 0, + }, + ]; +} + +async function capturePayload( + model: Model, + context: Context, + transform?: (payload: unknown, model: Model) => unknown | Promise, +): Promise> { + let payload: Record | undefined; + await cuaModels() + .streamSimple(model, context, { + apiKey: "test", + onPayload: async (value, payloadModel) => { + const transformed = transform + ? ((await transform(value, payloadModel)) ?? value) + : value; + payload = transformed as Record; + return transformed; + }, + }) + .result(); + if (!payload) throw new Error("payload was not captured"); + return payload; +} + +describe("message-anchored dynamic tool payloads", () => { + it("uses OpenAI tool search while preserving response threading", async () => { + const model = getCuaModel("openai:gpt-5.5"); + const payload = await capturePayload(model, { + systemPrompt: "test", + tools: [addTool, dynamicTool], + messages: addMessages(model.api, "resp_add"), + }); + expect(payload.store).toBe(true); + expect(payload.previous_response_id).toBe("resp_add"); + const searchOutput = payload.input.find((item: any) => item.type === "tool_search_output"); + expect(searchOutput.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "learned_tool", defer_loading: true })]), + ); + expect(payload.input).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "tool_search_call" }), + expect.objectContaining({ type: "tool_search_output" }), + ]), + ); + }); + + it("eagerly declares the tool after the add marker is pruned by the next response anchor", async () => { + const model = getCuaModel("openai:gpt-5.5"); + const messages = addMessages(model.api, "resp_add"); + messages.push( + { + role: "assistant", + content: [{ type: "toolCall", id: "learn_1", name: "learned_tool", arguments: {} }], + api: model.api, + provider: "openai", + model: model.id, + responseId: "resp_learned", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "toolUse", + timestamp: 0, + }, + { role: "toolResult", toolCallId: "learn_1", toolName: "learned_tool", content: [], isError: false, timestamp: 0 }, + ); + const payload = await capturePayload(model, { systemPrompt: "test", tools: [addTool, dynamicTool], messages }); + expect(payload.previous_response_id).toBe("resp_learned"); + expect(payload.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "learned_tool" })]), + ); + expect(payload.tools.find((tool: any) => tool.name === "learned_tool").defer_loading).toBeUndefined(); + }); + + it("uses Anthropic references for supported models and eager tools for Haiku", async () => { + const supported = getCuaModel("anthropic:claude-sonnet-4-5"); + const supportedPayload = await capturePayload(supported, { + systemPrompt: "test", + tools: [addTool, dynamicTool], + messages: addMessages(supported.api), + }); + expect(supportedPayload.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "learned_tool", defer_loading: true })]), + ); + expect(JSON.stringify(supportedPayload.messages)).toContain("tool_reference"); + + const haiku = { + ...supported, + id: "claude-haiku-4", + compat: { ...supported.compat, supportsToolReferences: false }, + }; + const haikuPayload = await capturePayload(haiku, { + systemPrompt: "test", + tools: [addTool, dynamicTool], + messages: addMessages(haiku.api), + }); + expect(JSON.stringify(haikuPayload.messages)).not.toContain("tool_reference"); + expect(haikuPayload.tools).toEqual( + expect.arrayContaining([expect.objectContaining({ name: "learned_tool" })]), + ); + }); + + it("preserves dynamic tools and beta headers through Anthropic native routing", async () => { + let requestHeaders: Record = {}; + const server = createServer((request, response) => { + requestHeaders = request.headers; + response.writeHead(500, { "content-type": "application/json" }); + response.end('{"error":{"message":"captured"}}'); + }); + await new Promise((resolve) => + server.listen(0, "127.0.0.1", resolve), + ); + try { + const address = server.address(); + if (!address || typeof address === "string") + throw new Error("test server did not bind"); + const spec = resolveCuaRuntimeSpec( + "anthropic:claude-sonnet-4-5", + { nativeTool: { type: "browser_20260701" } }, + ); + const model = { + ...spec.model, + api: ANTHROPIC_NATIVE_BROWSER_MESSAGES_API, + baseUrl: `http://127.0.0.1:${address.port}`, + } as Model; + const nativePlaceholder = { + ...dynamicTool, + ...spec.toolDefinitions[0], + } as AgentTool; + const payload = await capturePayload( + model, + { + systemPrompt: "test", + tools: [nativePlaceholder, addTool, dynamicTool], + messages: addMessages("anthropic-messages"), + }, + spec.onPayload, + ); + expect(payload.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "learned_tool", + defer_loading: true, + }), + ]), + ); + expect(payload.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "browser_20260701" }), + ]), + ); + expect(requestHeaders["anthropic-beta"]).toContain( + "browser-use-2026-07-01", + ); + } finally { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ); + } + }); +}); diff --git a/packages/cli/README.md b/packages/cli/README.md index 2e12229..7a8b118 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -43,7 +43,7 @@ cua do "buy a pair of socks on amazon" --max-steps 20 cua models cua models -p openai cua --print --model openai:gpt-5.5 "..." -cua --print --model anthropic:claude-opus-4-7 "..." +cua --print --model anthropic:claude-opus-4-8 "..." cua --print --model google:gemini-3-flash-preview "..." cua --print --model yutori:n1.5-latest "..." @@ -170,9 +170,11 @@ loader are appended to the system prompt and listed in the TUI's context files still load, since they describe the project rather than add agent capabilities. -pi *extensions* are not executed by `cua`: extensions bind into pi's -`AgentSession`, and `cua` drives the lower-level `AgentHarness` -directly. Installed-package skills and context still load. +Project and user pi extensions load at startup and can be refreshed with +`/reload`. `--self-extend` additionally exposes `add_tool`, which accepts one +constrained tool definition, persists it under `.agents/extensions`, and makes +it callable on the next model turn in the same run. Authored execute code is +trusted local code, not sandboxed. ## Image protocol diff --git a/packages/cli/package.json b/packages/cli/package.json index 8a5caf4..00daca4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,8 +34,8 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.3", - "@earendil-works/pi-tui": "0.80.3", + "@earendil-works/pi-coding-agent": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz", + "@earendil-works/pi-tui": "https://raw.githubusercontent.com/kernel/cua/eac28f9f4992c648d0d046dfe62550bce643ff85/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz", "@onkernel/cua-agent": "0.6.0", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0" diff --git a/packages/cli/src/action/harness-runner.ts b/packages/cli/src/action/harness-runner.ts index bd883a8..785cef1 100644 --- a/packages/cli/src/action/harness-runner.ts +++ b/packages/cli/src/action/harness-runner.ts @@ -107,24 +107,14 @@ export async function runAction( } async function maybeInitialScreenshot(opts: HarnessRunOptions): Promise { + // `skipInitialScreenshot` is decided once at startup (before extensions load), + // so an extension's startup message can't suppress the user's first-turn frame. if (opts.skipInitialScreenshot) return undefined; - const hasPriorTurn = await sessionHasPriorTurn(opts.session); - if (hasPriorTurn) return undefined; const png = await captureScreenshot(opts.browserHandle.client, opts.browserHandle.browser.session_id); if (!png) return undefined; return [{ type: "image", data: png.toString("base64"), mimeType: "image/png" }]; } -async function sessionHasPriorTurn(session: Session): Promise { - const entries = await session.getBranch(); - for (const entry of entries) { - if (entry.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) { - return true; - } - } - return false; -} - function textFromAssistant(message: AssistantMessage): string { const parts: string[] = []; for (const block of message.content) { diff --git a/packages/cli/src/cli-harness.ts b/packages/cli/src/cli-harness.ts index 2de6d20..a228a0b 100644 --- a/packages/cli/src/cli-harness.ts +++ b/packages/cli/src/cli-harness.ts @@ -10,13 +10,18 @@ import { type CuaMode, type CuaModelRef, type CuaNativeToolSpec, + type ImageContent, parseCuaModelRef, requireCuaEnvApiKey, resolveCuaRuntimeSpec, } from "@onkernel/cua-ai"; import { parseArgs } from "node:util"; import { stderr, stdout } from "node:process"; -import type { CuaBrowserHandle } from "./harness-browser"; +import { captureScreenshot, type CuaBrowserHandle } from "./harness-browser"; +import { + loadHarnessExtensions, + type HarnessExtensions, +} from "./extensions/setup"; import { type ActionRequest, type ModelActionType, @@ -178,12 +183,14 @@ export interface HarnessCliFlags { resumePicker: boolean; noSession: boolean; noSkills: boolean; + noExtensions: boolean; debugTui: boolean; jsonlIncludeDeltas: boolean; jsonlIncludeImages: boolean; playwright: boolean; mode?: string; nativeTool?: string; + selfExtend: boolean; model?: string; thinking?: string; browserProfile?: string; @@ -359,6 +366,15 @@ interface HarnessRuntime { harness: ReturnType; provider: string; modelRef: CuaModelRef; + /** + * Whether the first prompt should skip the initial browser screenshot. + * Decided once here, before extensions load, so an extension that sends a + * user message during startup can't flip the live prior-turn check and leave + * the user's real first prompt without the screenshot. + */ + skipInitialScreenshot: boolean; + /** Loaded pi-extension host. Undefined with --no-extensions or an untrusted project + no global extensions. */ + host?: HarnessExtensions; } export interface SetupHarnessRuntimeOptions { @@ -482,6 +498,39 @@ async function finishHarnessRuntime( modelBaseUrl: baseUrlOverride, }); + const handle = provisioned.handle; + // Raw capture: the host gates this behind the session's prior-turn state, so + // it must not re-apply the CLI's own first-prompt guard. + const initialScreenshot = async (): Promise => { + const png = await captureScreenshot(handle.client, handle.browser.session_id); + return png ? [{ type: "image", data: png.toString("base64"), mimeType: "image/png" }] : undefined; + }; + // A throwing extension load (e.g. a tool name colliding with a base tool) must + // not leak the already-provisioned browser handle: the caller's finally only + // runs once this returns, so close the handle here before rethrowing. The + // first-turn screenshot decision reads the session, so keep it inside the same + // guard. + let host: HarnessExtensions | undefined; + let skipInitialScreenshot: boolean; + try { + // Decide the first-turn screenshot before extensions load: a resumed session + // already has turns, and capturing it now (rather than re-reading the live + // transcript at prompt time) keeps an extension's startup sendUserMessage from + // flipping the check and leaving the user's real first prompt without it. + skipInitialScreenshot = resolved?.resumed === true || (await sessionHasPriorTurn(session)); + host = await loadHarnessExtensions({ + harness, + session, + cwd, + noExtensions: flags.noExtensions, + initialScreenshot, + selfExtend: flags.selfExtend, + }); + } catch (err) { + await provisioned.handle.close().catch(() => {}); + throw err; + } + return { handle: provisioned.handle, resolved, @@ -491,9 +540,20 @@ async function finishHarnessRuntime( harness, provider, modelRef: auth.modelRef, + skipInitialScreenshot, + host, }; } +async function sessionHasPriorTurn(session: Session): Promise { + const entries = await session.getBranch(); + return entries.some( + (entry) => + entry.type === "message" && + (entry.message.role === "user" || entry.message.role === "assistant"), + ); +} + function hasExplicitSessionFlag(flags: HarnessCliFlags): boolean { return ( !!flags.sessionRef || @@ -563,13 +623,21 @@ export async function runPrintCommand(prompt: string, flags: HarnessCliFlags): P provider: runtime.provider, prompt, skills: runtime.skills, - skipInitialScreenshot: runtime.resolved?.resumed === true, + skipInitialScreenshot: runtime.skipInitialScreenshot, verbose: flags.verbose, jsonlMode, jsonlIncludeDeltas: flags.jsonlIncludeDeltas, jsonlIncludeImages: flags.jsonlIncludeImages, }); } finally { + // Dispose before closing the handle: extensions receive session_shutdown + // during dispose and may call back into the harness while the browser lives. + // Separate try blocks so a throwing extension shutdown never skips close(). + try { + await runtime.host?.dispose(); + } catch (err) { + stderr.write(`[cua] cleanup warning: ${(err as Error).message}\n`); + } try { await runtime.handle.close(); } catch (err) { @@ -600,10 +668,16 @@ export async function runInteractiveCommand( debugTui: flags.debugTui, resumed: runtime.resolved?.resumed === true, transcriptPath: runtime.resolved?.transcriptPath, - skipInitialScreenshot: runtime.resolved?.resumed === true, + skipInitialScreenshot: runtime.skipInitialScreenshot, namedSession: flags.namedSession, + host: runtime.host, }); } finally { + try { + await runtime.host?.dispose(); + } catch (err) { + stderr.write(`[cua] cleanup warning: ${(err as Error).message}\n`); + } try { await runtime.handle.close(); } catch (err) { @@ -626,10 +700,15 @@ export async function runActionCommand( harness: runtime.harness, browserHandle: runtime.handle, session: runtime.session, - skipInitialScreenshot: runtime.resolved?.resumed === true, + skipInitialScreenshot: runtime.skipInitialScreenshot, }); return emitCompact(res); } finally { + try { + await runtime.host?.dispose(); + } catch (err) { + stderr.write(`[cua] cleanup warning: ${(err as Error).message}\n`); + } try { await runtime.handle.close(); } catch (err) { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index e474b56..a94a5dc 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -51,7 +51,7 @@ Options: match exactly one entry in \`cua models\`. Recommended: openai: openai:gpt-5.5 - anthropic: anthropic:claude-opus-4-7 + anthropic: anthropic:claude-opus-4-8 google: google:gemini-3-flash-preview tzafon: tzafon:tzafon.northstar-cua-fast yutori: yutori:n1.5-latest @@ -72,6 +72,8 @@ Options: --native-tool Drive an Anthropic model through its native tool schema: computer_20260701 (requires --mode computer) or browser_20260701 (requires --mode browser) + --self-extend Expose add_tool for adding one durable project tool; + the tool is active on the next model turn in the same run --out Output file for screenshot subcommand --filter Restrict \`cua snapshot\` to interactive elements -o, --output Output format for --print: text (default) | jsonl @@ -91,6 +93,9 @@ Options: /.agents/skills/, the pi agent dir (~/.pi/agent/), and pi-installed packages. -ns, --no-skills Disable skill discovery entirely + --no-extensions Disable pi extensions, which otherwise load from + /.agents/extensions, /.pi/extensions, + and the pi agent dir (~/.pi/agent/extensions/) --debug-tui Enable TUI render diagnostics for manual repros -v, --verbose Verbose progress output to stderr -h, --help Show this help @@ -123,12 +128,14 @@ interface CliFlags { resumePicker: boolean; noSession: boolean; noSkills: boolean; + noExtensions: boolean; debugTui: boolean; jsonlIncludeDeltas: boolean; jsonlIncludeImages: boolean; playwright: boolean; mode?: string; nativeTool?: string; + selfExtend: boolean; model?: string; thinking?: string; browserProfile?: string; @@ -177,6 +184,7 @@ function parseCliArgs(argv: string[]): CliFlags { "no-session": { type: "boolean", default: false }, skill: { type: "string", multiple: true, default: [] }, "no-skills": { type: "boolean", default: false }, + "no-extensions": { type: "boolean", default: false }, "debug-tui": { type: "boolean", default: false }, output: { type: "string", short: "o" }, "jsonl-include-deltas": { type: "boolean", default: false }, @@ -184,6 +192,7 @@ function parseCliArgs(argv: string[]): CliFlags { playwright: { type: "boolean", default: false }, mode: { type: "string" }, "native-tool": { type: "string" }, + "self-extend": { type: "boolean", default: false }, }, allowPositionals: true, strict: true, @@ -223,6 +232,7 @@ function parseCliArgs(argv: string[]): CliFlags { resumePicker: !!parsed.values.resume, noSession: !!parsed.values["no-session"], noSkills: !!parsed.values["no-skills"], + noExtensions: !!parsed.values["no-extensions"], debugTui: !!parsed.values["debug-tui"], model: parsed.values.model as string | undefined, thinking: parsed.values.thinking as string | undefined, @@ -243,6 +253,7 @@ function parseCliArgs(argv: string[]): CliFlags { playwright: !!parsed.values.playwright, mode: parsed.values.mode as string | undefined, nativeTool: parsed.values["native-tool"] as string | undefined, + selfExtend: !!parsed.values["self-extend"], positionals: parsed.positionals, }; } @@ -255,12 +266,14 @@ function toHarnessFlags(flags: CliFlags): HarnessCliFlags { resumePicker: flags.resumePicker, noSession: flags.noSession, noSkills: flags.noSkills, + noExtensions: flags.noExtensions, debugTui: flags.debugTui, jsonlIncludeDeltas: flags.jsonlIncludeDeltas, jsonlIncludeImages: flags.jsonlIncludeImages, playwright: flags.playwright, mode: flags.mode, nativeTool: flags.nativeTool, + selfExtend: flags.selfExtend, model: flags.model, thinking: flags.thinking, browserProfile: flags.browserProfile, diff --git a/packages/cli/src/extensions/add-tool.ts b/packages/cli/src/extensions/add-tool.ts new file mode 100644 index 0000000..c74be10 --- /dev/null +++ b/packages/cli/src/extensions/add-tool.ts @@ -0,0 +1,287 @@ +import type { AgentToolResult } from "@onkernel/cua-agent"; +import { + createSyntheticSourceInfo, + discoverAndLoadExtensions, + type RegisteredTool, +} from "@earendil-works/pi-coding-agent"; +import { link, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +export interface AddToolInput { + name: string; + label?: string; + description: string; + parameters: Record; + execute: string; +} + +export interface AddToolDetails { + written: string; + valid: true; + addedToolNames: string[]; +} + +export interface AddToolRegistrationOptions { + cwd: string; + extensionRoot: string | undefined; + hasToolName(name: string): boolean; + installTool(registration: RegisteredTool): Promise; +} + +const ADD_TOOL_NAME = "add_tool"; +const TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/; + +const ADD_TOOL_DESCRIPTION = [ + "Add one trusted project-local tool and make it available immediately.", + "The definition is validated, persisted beneath .agents/extensions, and", + "activated before this call returns, so it can be called on the next model turn.", + "The execute field must be one async function expression. This capability is not", + "a sandbox: execute code has the same Node.js access as other local extensions.", +].join("\n"); + +const ADD_TOOL_PARAMETERS = { + type: "object", + properties: { + name: { + type: "string", + description: "provider-safe tool name (letters, digits, _ and -)", + }, + label: { type: "string", description: "display label; defaults to name" }, + description: { type: "string", description: "non-empty tool description" }, + parameters: { + type: "object", + description: 'JSON Schema with top-level type "object"', + }, + execute: { + type: "string", + description: + "one async function expression with signature (toolCallId, params, signal, onUpdate)", + }, + }, + required: ["name", "description", "parameters", "execute"], + additionalProperties: false, +} as const; + +/** Build the normal Pi tool registration used by the compatibility host. */ +export function createAddToolRegistration( + options: AddToolRegistrationOptions, +): RegisteredTool { + return { + definition: { + name: ADD_TOOL_NAME, + label: "Add tool", + description: ADD_TOOL_DESCRIPTION, + parameters: ADD_TOOL_PARAMETERS, + executionMode: "sequential", + execute: async ( + _toolCallId, + rawInput, + ): Promise> => addTool(options, rawInput), + }, + sourceInfo: createSyntheticSourceInfo(ADD_TOOL_NAME, { + source: "cua --self-extend", + scope: "project", + baseDir: options.cwd, + }), + }; +} + +async function addTool( + options: AddToolRegistrationOptions, + input: unknown, +): Promise> { + const extensionRoot = options.extensionRoot; + if (!extensionRoot) + throw new Error("no project extension directory configured for add_tool"); + const normalized = validateAddToolInput(input); + const target = join(extensionRoot, `${normalized.name}.ts`); + if (options.hasToolName(normalized.name)) { + throw new Error(`tool name "${normalized.name}" already exists`); + } + + await mkdir(extensionRoot, { recursive: true }); + const stagingDir = await mkdtemp(join(extensionRoot, ".add-tool-")); + const stagedFile = join(stagingDir, `${normalized.name}.ts`); + try { + await writeFile(stagedFile, renderToolExtension(normalized), { + encoding: "utf8", + flag: "wx", + }); + const registered = await trialLoadTool( + stagedFile, + normalized.name, + stagingDir, + ); + try { + await link(stagedFile, target); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "EEXIST") { + throw new Error(`extension already exists at ${target}`); + } + throw error; + } + + try { + await options.installTool({ + definition: registered.definition, + sourceInfo: createSyntheticSourceInfo(target, { + source: target, + scope: "project", + baseDir: extensionRoot, + }), + }); + } catch (error) { + await rm(target, { force: true }); + throw error; + } + + return { + content: [{ type: "text", text: `added ${normalized.name} at ${target}` }], + details: { + written: target, + valid: true, + addedToolNames: [normalized.name], + }, + }; + } finally { + await rm(stagingDir, { recursive: true, force: true }); + } +} + +async function trialLoadTool( + filePath: string, + expectedName: string, + isolatedRoot: string, +): Promise { + const result = await discoverAndLoadExtensions( + [filePath], + isolatedRoot, + isolatedRoot, + ); + if (result.errors.length > 0) { + throw new Error( + `tool validation failed: ${result.errors.map((entry) => entry.error).join("; ")}`, + ); + } + const registrations = result.extensions.flatMap((extension) => [ + ...extension.tools.values(), + ]); + if ( + registrations.length !== 1 || + registrations[0]?.definition.name !== expectedName + ) { + throw new Error( + `generated extension must register exactly one tool named "${expectedName}"`, + ); + } + const registration = registrations[0]; + if ( + typeof registration.definition.execute !== "function" || + registration.definition.execute.constructor.name !== "AsyncFunction" + ) { + throw new Error("execute must be one async function expression"); + } + return registration; +} + +function validateAddToolInput(input: unknown): Required { + if (!input || typeof input !== "object" || Array.isArray(input)) { + throw new Error("tool definition must be an object"); + } + const candidate = input as Record; + if ( + typeof candidate.name !== "string" || + !TOOL_NAME_PATTERN.test(candidate.name) + ) { + throw new Error( + "name must start with a letter, contain only letters, digits, _ or -, and be at most 64 characters", + ); + } + const label = candidate.label ?? candidate.name; + if (typeof label !== "string" || label.trim().length === 0) + throw new Error("label must be non-empty"); + if ( + typeof candidate.description !== "string" || + candidate.description.trim().length === 0 + ) { + throw new Error("description must be non-empty"); + } + if ( + !candidate.parameters || + typeof candidate.parameters !== "object" || + Array.isArray(candidate.parameters) || + (candidate.parameters as Record).type !== "object" + ) { + throw new Error( + 'parameters must be a JSON-serializable object schema with top-level type "object"', + ); + } + try { + JSON.stringify(candidate.parameters); + } catch { + throw new Error("parameters must be JSON-serializable"); + } + if ( + typeof candidate.execute !== "string" || + !/^(?:\s*)async\b/.test(candidate.execute) || + hasTopLevelComma(candidate.execute) + ) { + throw new Error("execute must be one async function expression"); + } + return { + name: candidate.name, + label, + description: candidate.description, + parameters: candidate.parameters as Record, + execute: candidate.execute, + }; +} + +function hasTopLevelComma(source: string): boolean { + let parens = 0; + let braces = 0; + let brackets = 0; + let quote: "'" | '"' | "`" | undefined; + let escaped = false; + for (const character of source) { + if (quote) { + if (escaped) escaped = false; + else if (character === "\\") escaped = true; + else if (character === quote) quote = undefined; + continue; + } + if (character === "'" || character === '"' || character === "`") { + quote = character; + continue; + } + if (character === "(") parens += 1; + else if (character === ")") parens -= 1; + else if (character === "{") braces += 1; + else if (character === "}") braces -= 1; + else if (character === "[") brackets += 1; + else if (character === "]") brackets -= 1; + else if (character === "," && parens === 0 && braces === 0 && brackets === 0) + return true; + } + return false; +} + +export function renderToolExtension(input: Required): string { + return [ + `const name = ${JSON.stringify(input.name)};`, + `const label = ${JSON.stringify(input.label)};`, + `const description = ${JSON.stringify(input.description)};`, + `const parameters = ${JSON.stringify(input.parameters)};`, + "", + "export default function (pi) {", + "\tpi.registerTool({", + "\t\tname,", + "\t\tlabel,", + "\t\tdescription,", + "\t\tparameters,", + `\t\texecute: (${input.execute}),`, + "\t});", + "}", + "", + ].join("\n"); +} diff --git a/packages/cli/src/extensions/compat/README.md b/packages/cli/src/extensions/compat/README.md new file mode 100644 index 0000000..59e6556 --- /dev/null +++ b/packages/cli/src/extensions/compat/README.md @@ -0,0 +1,15 @@ +# AgentHarness extension compatibility + +Pi currently binds extensions to `AgentSession`, not `AgentHarness`. This directory +contains the temporary adapter used by the CUA CLI: + +- `context.ts` implements Pi extension context actions over the harness. +- `hooks.ts` forwards harness hooks and lifecycle events to `ExtensionRunner`. +- `tool-registry.ts` reconciles extension tools with CUA tool/model changes. +- `host.ts` owns discovery, lifecycle, and manual reload. + +Pi's `packages/agent/docs/hooks.md` designs generic `AgentHarness` hooks and its +`packages/agent/docs/agent-harness.md` plans a later coding-agent migration onto +that hook/session facade. When those APIs are public, replace this directory from +`../setup.ts`; `../add-tool.ts` and its standard Pi extension artifacts should not +need to move with it. diff --git a/packages/cli/src/extensions/compat/context.ts b/packages/cli/src/extensions/compat/context.ts new file mode 100644 index 0000000..3de8dbb --- /dev/null +++ b/packages/cli/src/extensions/compat/context.ts @@ -0,0 +1,168 @@ +import type { AgentHarness, Session } from "@onkernel/cua-agent"; +import { + createSyntheticSourceInfo, + type ExtensionActions, + type ExtensionCommandContextActions, + type ExtensionContextActions, + type ToolInfo, +} from "@earendil-works/pi-coding-agent"; + +/** + * Read/write bridge between the runner's `ExtensionActions` (pi.* API surface) + * and the harness + the `Session` the harness was constructed with. + * + * Session entry writes are async on the harness `Session` but the action + * handlers are synchronous, so the appends are fired without awaiting. The + * runner only needs the call to be enqueued, matching how pi forwards these to + * its own session. + */ +export interface HarnessContextBindings { + refreshTools: () => void; + getActiveTools: () => string[]; + /** Forward user text through the host's first-turn screenshot prompt path. */ + sendUserMessage: (text: string) => Promise; + /** Apply an active-tool set, recording extension-tool opt-outs in the host. */ + setActiveTools: (names: string[]) => Promise; + /** Synchronous mirror of the session name (kept because the action getter is sync). */ + getSessionName: () => string | undefined; + /** Record the latest session name set through the action surface. */ + setSessionName: (name: string | undefined) => void; +} + +export function makeExtensionActions( + harness: AgentHarness, + session: Session, + bindings: HarnessContextBindings, +): ExtensionActions { + return { + sendMessage(message): void { + void session.appendCustomMessageEntry( + message.customType, + message.content, + message.display ?? true, + message.details, + ); + }, + sendUserMessage(content): void { + const text = typeof content === "string" ? content : textPartsOf(content); + void bindings.sendUserMessage(text); + }, + appendEntry(customType, data): void { + void session.appendCustomEntry(customType, data); + }, + setSessionName(name): void { + bindings.setSessionName(name); + void session.appendSessionName(name); + }, + getSessionName(): string | undefined { + return bindings.getSessionName(); + }, + // Labels are a TUI-only affordance (entry bookmarking); no headless sink. + setLabel(): void {}, + getActiveTools(): string[] { + return bindings.getActiveTools(); + }, + getAllTools(): ToolInfo[] { + return harness.getTools().map( + (tool): ToolInfo => ({ + name: tool.name, + description: tool.description, + parameters: tool.parameters, + promptGuidelines: undefined, + sourceInfo: createSyntheticSourceInfo(tool.name, { source: "harness" }), + }), + ); + }, + setActiveTools(names): void { + void bindings.setActiveTools(names); + }, + refreshTools(): void { + bindings.refreshTools(); + }, + // Slash commands are Tier B; none are surfaced from this host. + getCommands(): never[] { + return []; + }, + async setModel(model): Promise { + await harness.setModel(model); + return true; + }, + getThinkingLevel() { + return harness.getThinkingLevel(); + }, + setThinkingLevel(level): void { + void harness.setThinkingLevel(level); + }, + }; +} + +export function makeExtensionContextActions( + harness: AgentHarness, + state: { isIdle: () => boolean; getSignal: () => AbortSignal | undefined; shutdown: () => void }, +): ExtensionContextActions { + return { + getModel() { + return harness.getModel(); + }, + isIdle() { + return state.isIdle(); + }, + // Headless host trusts its cwd; project-trust prompts are a TUI concern. + isProjectTrusted(): boolean { + return true; + }, + getSignal() { + return state.getSignal(); + }, + abort(): void { + void harness.abort(); + }, + hasPendingMessages(): boolean { + return false; + }, + shutdown(): void { + state.shutdown(); + }, + getContextUsage() { + return undefined; + }, + compact(options): void { + void harness.compact(options?.customInstructions); + }, + getSystemPrompt(): string { + return ""; + }, + }; +} + +export function makeExtensionCommandContextActions( + harness: AgentHarness, + reload: () => Promise, +): ExtensionCommandContextActions { + return { + waitForIdle() { + return harness.waitForIdle(); + }, + async navigateTree(targetId, options) { + const result = await harness.navigateTree(targetId, options); + return { cancelled: result.cancelled }; + }, + reload, + newSession(): never { + throw new Error("newSession is unsupported on this harness host"); + }, + fork(): never { + throw new Error("fork is unsupported on this harness host"); + }, + switchSession(): never { + throw new Error("switchSession is unsupported on this harness host"); + }, + }; +} + +function textPartsOf(content: ReadonlyArray<{ type: string; text?: string }>): string { + return content + .filter((part) => part.type === "text" && typeof part.text === "string") + .map((part) => part.text ?? "") + .join(""); +} diff --git a/packages/cli/src/extensions/compat/hooks.ts b/packages/cli/src/extensions/compat/hooks.ts new file mode 100644 index 0000000..3ef7be9 --- /dev/null +++ b/packages/cli/src/extensions/compat/hooks.ts @@ -0,0 +1,184 @@ +import type { AgentHarness } from "@onkernel/cua-agent"; +import type { ExtensionRunner } from "@earendil-works/pi-coding-agent"; + +/** + * State updated while forwarding harness hooks into the extension runner. + * `turnIndex` is host-owned because the harness `turn_start`/`turn_end` events + * do not carry one, while the extension `TurnStartEvent`/`TurnEndEvent` require + * it. `isIdle` is derived from `agent_start`/`agent_end` because the harness has + * no synchronous idle predicate. + */ +export interface ExtensionHookState { + turnIndex: number; + isIdle: boolean; +} + +/** + * Wire harness events into the runner's extension-event emitters and return a + * single teardown function that detaches every listener. + * + * Two channels are used, matching how the harness dispatches: + * - `subscribe()` (catch-all) for loop events and own observe-only events. + * Tool reconciliation also runs here after `model_update`. + * - `on(type)` for the participating own events whose reduced result the + * harness applies (context, before_provider_payload, tool_call, tool_result). + */ +export function installExtensionHooks( + harness: AgentHarness, + runner: ExtensionRunner, + state: ExtensionHookState, + reapplyTools: () => Promise, +): () => void { + const unsubscribes: Array<() => void> = []; + + unsubscribes.push( + harness.subscribe(async (event) => { + switch (event.type) { + case "agent_start": + state.turnIndex = 0; + state.isIdle = false; + await runner.emit({ type: "agent_start" }); + break; + case "agent_end": + state.isIdle = true; + await runner.emit({ type: "agent_end", messages: event.messages }); + break; + case "turn_start": + await runner.emit({ type: "turn_start", turnIndex: state.turnIndex, timestamp: Date.now() }); + break; + case "turn_end": + await runner.emit({ + type: "turn_end", + turnIndex: state.turnIndex, + message: event.message, + toolResults: event.toolResults, + }); + state.turnIndex += 1; + break; + case "message_start": + await runner.emit({ type: "message_start", message: event.message }); + break; + case "message_update": + await runner.emit({ + type: "message_update", + message: event.message, + assistantMessageEvent: event.assistantMessageEvent, + }); + break; + case "tool_execution_start": + await runner.emit({ + type: "tool_execution_start", + toolCallId: event.toolCallId, + toolName: event.toolName, + args: event.args, + }); + break; + case "tool_execution_update": + await runner.emit({ + type: "tool_execution_update", + toolCallId: event.toolCallId, + toolName: event.toolName, + args: event.args, + partialResult: event.partialResult, + }); + break; + case "tool_execution_end": + await runner.emit({ + type: "tool_execution_end", + toolCallId: event.toolCallId, + toolName: event.toolName, + result: event.result, + isError: event.isError, + }); + break; + case "model_update": + await runner.emit({ + type: "model_select", + model: event.model, + previousModel: event.previousModel, + source: event.source, + }); + // CuaAgentHarness.setModel rebuilds the tool list from + // construction-time tools, dropping runtime-registered extension + // tools. Re-apply the union after the rebuild has emitted + // model_update. setModel emits only model_update (never + // tools_update), so this triggers exactly one tools_update. + await reapplyTools(); + break; + case "thinking_level_update": + await runner.emit({ + type: "thinking_level_select", + level: event.level, + previousLevel: event.previousLevel, + }); + break; + case "after_provider_response": + await runner.emit({ + type: "after_provider_response", + status: event.status, + headers: event.headers, + }); + break; + default: + break; + } + }), + ); + + unsubscribes.push( + harness.on("context", async (event) => { + const messages = await runner.emitContext(event.messages); + return { messages }; + }), + ); + + unsubscribes.push( + harness.on("before_provider_payload", async (event) => { + const payload = await runner.emitBeforeProviderRequest(event.payload); + return { payload }; + }), + ); + + unsubscribes.push( + harness.on("tool_call", async (event) => { + if (!runner.hasHandlers("tool_call")) return undefined; + // Pass event.input by reference: tool_call handlers mutate it in place + // to patch arguments, and the harness re-reads the same object. + const result = await runner.emitToolCall({ + type: "tool_call", + toolName: event.toolName, + toolCallId: event.toolCallId, + input: event.input, + }); + return result ? { block: result.block, reason: result.reason } : undefined; + }), + ); + + unsubscribes.push( + harness.on("tool_result", async (event) => { + // Registration during tool execution must reach the harness before the + // next provider turn is prepared. + await reapplyTools(); + if (!runner.hasHandlers("tool_result")) return undefined; + const result = await runner.emitToolResult({ + type: "tool_result", + toolName: event.toolName, + toolCallId: event.toolCallId, + input: event.input, + content: event.content, + details: event.details, + isError: event.isError, + }); + if (!result) return undefined; + return { + content: result.content, + details: result.details, + isError: result.isError ?? event.isError, + }; + }), + ); + + return () => { + for (const unsubscribe of unsubscribes) unsubscribe(); + }; +} diff --git a/packages/cli/src/extensions/compat/host.ts b/packages/cli/src/extensions/compat/host.ts new file mode 100644 index 0000000..09bd5e8 --- /dev/null +++ b/packages/cli/src/extensions/compat/host.ts @@ -0,0 +1,353 @@ +import type { AgentHarness, AgentTool, Session } from "@onkernel/cua-agent"; +import type { ImageContent } from "@onkernel/cua-ai"; +import { + AuthStorage, + discoverAndLoadExtensions, + ExtensionRunner, + ModelRegistry, + SessionManager, + wrapRegisteredTool, +} from "@earendil-works/pi-coding-agent"; +import type { + ExtensionActions, + ExtensionCommandContextActions, + ExtensionContextActions, +} from "@earendil-works/pi-coding-agent"; +import { createAddToolRegistration } from "../add-tool"; +import { + makeExtensionActions, + makeExtensionCommandContextActions, + makeExtensionContextActions, +} from "./context"; +import { installExtensionHooks, type ExtensionHookState } from "./hooks"; +import { HarnessToolRegistry } from "./tool-registry"; + +export interface HarnessExtensionHostOptions { + harness: AgentHarness; + /** The same `Session` the harness was constructed with; used for entry writes. */ + session: Session; + cwd: string; + /** Extension paths passed straight to `discoverAndLoadExtensions`. */ + configuredPaths: string[]; + /** Agent config dir searched for `extensions/`. Pass a temp dir to isolate from `~/.agents`. */ + agentDir?: string; + /** Capture the first-turn screenshot for extension-initiated user messages. */ + initialScreenshot?: () => Promise; + /** Expose the project-local `add_tool` extension. */ + selfExtend?: boolean; +} + +export type ReloadOutcome = "reloaded" | "coalesced" | "disposed"; + +/** + * Compatibility host for Pi extensions on `AgentHarness`. + * + * Pi plans to move coding-agent extensions onto generic AgentHarness hooks and + * facades. Keep that temporary integration under `extensions/compat`; the CLI + * depends only on the interface exported by `extensions/setup`. + */ +export class HarnessExtensionHost { + private readonly harness: AgentHarness; + private readonly session: Session; + private readonly cwd: string; + private readonly configuredPaths: string[]; + private readonly agentDir?: string; + private readonly initialScreenshot?: () => Promise< + ImageContent[] | undefined + >; + private readonly selfExtend: boolean; + private readonly extensionRoot: string | undefined; + private readonly sessionManager: SessionManager; + private readonly modelRegistry: ModelRegistry; + private readonly tools: HarnessToolRegistry; + private readonly actions: ExtensionActions; + private readonly contextActions: ExtensionContextActions; + private readonly commandActions: ExtensionCommandContextActions; + + private runner: ExtensionRunner | undefined; + private teardownHooks: (() => void) | undefined; + private readonly hookState: ExtensionHookState = { turnIndex: 0, isIdle: true }; + private reloadPromise: Promise | undefined; + private shutdownRequested = false; + private disposed = false; + private teardownDone = false; + private loaded = false; + private startedUp = false; + private sessionName: string | undefined; + + loadErrors: Array<{ path: string; error: string }> = []; + + constructor(options: HarnessExtensionHostOptions) { + this.harness = options.harness; + this.session = options.session; + this.cwd = options.cwd; + this.configuredPaths = options.configuredPaths; + this.agentDir = options.agentDir; + this.initialScreenshot = options.initialScreenshot; + this.selfExtend = options.selfExtend ?? false; + this.extensionRoot = options.configuredPaths[0]; + this.sessionManager = SessionManager.inMemory(this.cwd); + this.modelRegistry = ModelRegistry.inMemory(AuthStorage.inMemory()); + this.tools = new HarnessToolRegistry(this.harness, (path, error) => + this.recordError(path, error), + ); + + this.actions = makeExtensionActions(this.harness, this.session, { + refreshTools: () => this.refreshTools(), + getActiveTools: () => this.tools.desiredActiveToolNames(this.runner), + sendUserMessage: (text) => this.promptUserMessage(text), + setActiveTools: (names) => this.tools.applyActiveTools(names), + getSessionName: () => this.sessionName, + setSessionName: (name) => { + this.sessionName = name; + }, + }); + this.contextActions = makeExtensionContextActions(this.harness, { + isIdle: () => this.hookState.isIdle, + getSignal: () => undefined, + shutdown: () => this.requestShutdown(), + }); + this.commandActions = makeExtensionCommandContextActions( + this.harness, + async () => { + await this.reload(); + }, + ); + } + + isDisposed(): boolean { + return this.disposed; + } + + async load(): Promise { + if (this.disposed) throw new Error("cannot load a disposed extension host"); + if (this.loaded) return; + await this.buildRunner(); + try { + const runner = this.runner!; + await this.tools.reapply(runner, () => runner === this.runner); + this.installHooks(); + await runner.emit({ type: "session_start", reason: "startup" }); + } catch (error) { + this.uninstallHooks(); + this.runner?.invalidate(); + this.runner = undefined; + throw error; + } + this.loaded = true; + this.startedUp = true; + if (this.shutdownRequested) await this.dispose(); + } + + reload(): Promise { + if (this.disposed) return Promise.resolve("disposed"); + if (this.reloadPromise) return Promise.resolve("coalesced"); + const operation = this.reloadNow(); + this.reloadPromise = operation; + const clear = () => { + if (this.reloadPromise === operation) this.reloadPromise = undefined; + }; + void operation.then(clear, clear); + return operation; + } + + async dispose(): Promise { + if (this.teardownDone) return; + this.shutdownRequested = true; + this.disposed = true; + await this.reloadPromise?.catch(() => {}); + await this.tools.waitForSettled(); + await this.disposeNow(); + } + + private async reloadNow(): Promise { + await this.harness.waitForIdle(); + if (this.disposed) return "disposed"; + + const previousRunner = this.runner; + const previousLoadErrors = this.loadErrors; + const flags = previousRunner?.getFlagValues() ?? new Map(); + const candidate = await this.createRunner(); + if (this.disposed) { + candidate.runner.invalidate(); + return "disposed"; + } + for (const [name, value] of flags) candidate.runner.setFlagValue(name, value); + + await previousRunner?.emit({ type: "session_shutdown", reason: "reload" }); + if (this.disposed || this.shutdownRequested) { + candidate.runner.invalidate(); + await this.disposeNow(); + return "disposed"; + } + + this.uninstallHooks(); + this.runner = candidate.runner; + this.loadErrors = candidate.loadErrors; + const registrySnapshot = this.tools.beginRunnerReplacement(candidate.hostTool); + try { + await this.tools.reapply( + candidate.runner, + () => candidate.runner === this.runner && !this.disposed, + ); + if (this.disposed || this.shutdownRequested) { + candidate.runner.invalidate(); + previousRunner?.invalidate(); + await this.disposeNow(); + return "disposed"; + } + this.installHooks(); + await candidate.runner.emit({ type: "session_start", reason: "reload" }); + if (this.disposed || this.shutdownRequested) { + previousRunner?.invalidate(); + await this.disposeNow(); + return "disposed"; + } + previousRunner?.invalidate(); + return "reloaded"; + } catch (error) { + candidate.runner.invalidate(); + this.uninstallHooks(); + this.runner = previousRunner; + this.loadErrors = previousLoadErrors; + this.tools.restore(registrySnapshot); + if (!this.disposed && previousRunner) { + await this.tools + .reapply(previousRunner, () => previousRunner === this.runner) + .catch(() => {}); + this.installHooks(); + await previousRunner.emit({ type: "session_start", reason: "reload" }); + } + throw error; + } + } + + private async disposeNow(): Promise { + if (this.teardownDone) return; + this.teardownDone = true; + this.shutdownRequested = true; + this.disposed = true; + this.uninstallHooks(); + await this.tools.removeAll().catch(() => {}); + const runner = this.runner; + await runner?.emit({ type: "session_shutdown", reason: "quit" }); + runner?.invalidate(); + this.runner = undefined; + } + + private async buildRunner(): Promise { + const built = await this.createRunner(); + this.loadErrors = built.loadErrors; + this.runner = built.runner; + this.tools.setHostTool(built.hostTool); + } + + private async createRunner(): Promise<{ + runner: ExtensionRunner; + hostTool: AgentTool | undefined; + loadErrors: Array<{ path: string; error: string }>; + }> { + const result = await discoverAndLoadExtensions( + this.configuredPaths, + this.cwd, + this.agentDir, + ); + const runner = new ExtensionRunner( + result.extensions, + result.runtime, + this.cwd, + this.sessionManager, + this.modelRegistry, + ); + runner.bindCore(this.actions, this.contextActions); + runner.bindCommandContext(this.commandActions); + runner.setUIContext(undefined, "print"); + + const hostTool = this.selfExtend + ? wrapRegisteredTool( + createAddToolRegistration({ + cwd: this.cwd, + extensionRoot: this.extensionRoot, + hasToolName: (name) => this.tools.hasToolName(name), + installTool: (registration) => + this.tools.installRuntimeTool( + registration, + runner, + () => runner === this.runner && !this.disposed, + ), + }), + runner, + ) + : undefined; + return { runner, hostTool, loadErrors: result.errors }; + } + + private refreshTools(): void { + const runner = this.runner; + if (!runner) return; + void this.tools + .reapply(runner, () => runner === this.runner && !this.disposed) + .catch((error) => + this.recordError( + "", + error instanceof Error ? error.message : String(error), + ), + ); + } + + private installHooks(): void { + const runner = this.runner; + if (!runner) return; + this.teardownHooks = installExtensionHooks( + this.harness, + runner, + this.hookState, + () => this.tools.reapply(runner, () => runner === this.runner), + ); + } + + private uninstallHooks(): void { + const teardown = this.teardownHooks; + this.teardownHooks = undefined; + teardown?.(); + } + + private recordError(path: string, error: string): void { + if (this.loadErrors.some((entry) => entry.path === path && entry.error === error)) { + return; + } + this.loadErrors.push({ path, error }); + } + + private requestShutdown(): void { + this.shutdownRequested = true; + if (!this.reloadPromise) void this.dispose(); + } + + private async promptUserMessage(text: string): Promise { + try { + const images = await this.maybeInitialScreenshot(); + await this.harness.prompt(text, images ? { images } : undefined); + } catch (error) { + this.recordError( + "", + error instanceof Error ? error.message : String(error), + ); + } + } + + private async maybeInitialScreenshot(): Promise { + if (!this.initialScreenshot || !this.startedUp) return undefined; + if (await sessionHasPriorTurn(this.session)) return undefined; + return this.initialScreenshot(); + } +} + +async function sessionHasPriorTurn(session: Session): Promise { + const entries = await session.getBranch(); + return entries.some( + (entry) => + entry.type === "message" && + (entry.message.role === "user" || entry.message.role === "assistant"), + ); +} diff --git a/packages/cli/src/extensions/compat/tool-registry.ts b/packages/cli/src/extensions/compat/tool-registry.ts new file mode 100644 index 0000000..3cfbca6 --- /dev/null +++ b/packages/cli/src/extensions/compat/tool-registry.ts @@ -0,0 +1,202 @@ +import type { AgentHarness, AgentTool } from "@onkernel/cua-agent"; +import { + type ExtensionRunner, + type RegisteredTool, + wrapRegisteredTool, + wrapRegisteredTools, +} from "@earendil-works/pi-coding-agent"; + +interface RegistrySnapshot { + hostTool: AgentTool | undefined; + extensionTools: AgentTool[]; + runtimeTools: Map; +} + +/** Tool registry kept beside hooks, matching Pi's planned harness split. */ +export class HarnessToolRegistry { + private hostTool: AgentTool | undefined; + private extensionTools: AgentTool[] = []; + private runtimeTools = new Map(); + private readonly inactiveExtensionTools = new Set(); + private update = Promise.resolve(); + + constructor( + private readonly harness: AgentHarness, + private readonly recordError: (path: string, error: string) => void, + ) {} + + setHostTool(tool: AgentTool | undefined): void { + this.hostTool = tool; + } + + hasToolName(name: string): boolean { + return ( + this.harness.getTools().some((tool) => tool.name === name) || + this.runtimeTools.has(name) + ); + } + + beginRunnerReplacement(hostTool: AgentTool | undefined): RegistrySnapshot { + const snapshot = this.snapshot(); + this.hostTool = hostTool; + this.extensionTools = [ + ...this.extensionTools, + ...this.runtimeTools.values(), + ]; + this.runtimeTools.clear(); + return snapshot; + } + + restore(snapshot: RegistrySnapshot): void { + this.hostTool = snapshot.hostTool; + this.extensionTools = snapshot.extensionTools; + this.runtimeTools = new Map(snapshot.runtimeTools); + } + + async installRuntimeTool( + registration: RegisteredTool, + runner: ExtensionRunner, + isCurrent: () => boolean, + ): Promise { + const name = registration.definition.name; + this.runtimeTools.set(name, wrapRegisteredTool(registration, runner)); + try { + await this.reapply(runner, isCurrent); + } catch (error) { + this.runtimeTools.delete(name); + await this.reapply(runner, isCurrent).catch(() => {}); + throw error; + } + } + + reapply( + runner: ExtensionRunner, + isCurrent: () => boolean, + ): Promise { + const next = this.update.then(() => this.applyNow(runner, isCurrent)); + this.update = next.catch(() => {}); + return next; + } + + async waitForSettled(): Promise { + await this.update.catch(() => {}); + } + + async removeAll(): Promise { + const merged = new Set(this.ownedTools().map((tool) => tool.name)); + if (merged.size === 0) return; + await this.mutateHarnessTools(async () => { + const base = this.harness + .getTools() + .filter((tool) => !merged.has(tool.name)); + const active = this.harness + .getActiveTools() + .map((tool) => tool.name) + .filter((name) => !merged.has(name)); + await this.harness.setTools(base, active); + }); + } + + desiredActiveToolNames(runner: ExtensionRunner | undefined): string[] { + const current = new Set( + this.harness.getActiveTools().map((tool) => tool.name), + ); + if (this.hostTool) current.add(this.hostTool.name); + for (const tool of [...this.extensionTools, ...this.runtimeTools.values()]) { + if (!this.inactiveExtensionTools.has(tool.name)) current.add(tool.name); + } + for (const registered of runner?.getAllRegisteredTools() ?? []) { + if (!this.inactiveExtensionTools.has(registered.definition.name)) { + current.add(registered.definition.name); + } + } + return [...current]; + } + + async applyActiveTools(names: string[]): Promise { + const active = new Set(names); + for (const tool of [...this.extensionTools, ...this.runtimeTools.values()]) { + if (active.has(tool.name)) this.inactiveExtensionTools.delete(tool.name); + else this.inactiveExtensionTools.add(tool.name); + } + await this.harness.setActiveTools(names); + } + + private snapshot(): RegistrySnapshot { + return { + hostTool: this.hostTool, + extensionTools: this.extensionTools, + runtimeTools: new Map(this.runtimeTools), + }; + } + + private ownedTools(): AgentTool[] { + return [this.hostTool, ...this.extensionTools, ...this.runtimeTools.values()].filter( + (tool): tool is AgentTool => tool !== undefined, + ); + } + + private async applyNow( + runner: ExtensionRunner, + isCurrent: () => boolean, + ): Promise { + if (!isCurrent()) return; + await this.mutateHarnessTools(async () => { + if (!isCurrent()) return; + const ownedBefore = new Set(this.ownedTools().map((tool) => tool.name)); + const base = this.harness + .getTools() + .filter((tool) => !ownedBefore.has(tool.name)); + const baseNames = new Set(base.map((tool) => tool.name)); + const reservedNames = new Set([ + ...(this.hostTool ? [this.hostTool.name] : []), + ...this.runtimeTools.keys(), + ]); + const diskTools = wrapRegisteredTools( + runner.getAllRegisteredTools(), + runner, + ).filter((tool) => { + const collidesWith = reservedNames.has(tool.name) + ? "a host-provided tool" + : baseNames.has(tool.name) + ? "a built-in tool" + : undefined; + if (!collidesWith) return true; + this.recordError( + tool.name, + `extension tool "${tool.name}" collides with ${collidesWith} and was dropped`, + ); + return false; + }); + const runtimeTools = [...this.runtimeTools.values()]; + const final = [ + ...base, + ...(this.hostTool ? [this.hostTool] : []), + ...diskTools, + ...runtimeTools, + ]; + const finalNames = new Set(final.map((tool) => tool.name)); + const activeNames = new Set( + this.harness + .getActiveTools() + .map((tool) => tool.name) + .filter((name) => finalNames.has(name)), + ); + if (this.hostTool) activeNames.add(this.hostTool.name); + for (const tool of [...diskTools, ...runtimeTools]) { + if (!this.inactiveExtensionTools.has(tool.name)) { + activeNames.add(tool.name); + } + } + await this.harness.setTools(final, [...activeNames]); + this.extensionTools = diskTools; + }); + } + + private mutateHarnessTools(mutation: () => Promise): Promise { + const harness = this.harness as AgentHarness & { + mutateTools?: (callback: () => Promise) => Promise; + }; + return harness.mutateTools ? harness.mutateTools(mutation) : mutation(); + } +} diff --git a/packages/cli/src/extensions/setup.ts b/packages/cli/src/extensions/setup.ts new file mode 100644 index 0000000..89129c6 --- /dev/null +++ b/packages/cli/src/extensions/setup.ts @@ -0,0 +1,54 @@ +import type { CuaAgentHarness, Session } from "@onkernel/cua-agent"; +import type { ImageContent } from "@onkernel/cua-ai"; +import { getAgentDir } from "@earendil-works/pi-coding-agent"; +import { join } from "node:path"; +import { + HarnessExtensionHost, + type ReloadOutcome, +} from "./compat/host"; + +/** Stable CLI-facing surface; the AgentHarness compatibility host stays private. */ +export interface HarnessExtensions { + readonly loadErrors: Array<{ path: string; error: string }>; + isDisposed(): boolean; + reload(): Promise; + dispose(): Promise; +} + +/** + * Resolve extension directories and construct + load a {@link HarnessExtensionHost}. + * + * Global extensions (`/extensions`) and project-local extensions + * (`/.agents/extensions` plus the loader's implicit `/.pi/extensions` + * scan) all load on every run; `--no-extensions` opts out entirely. This is the + * substrate for durable project tools: `add_tool` publishes beneath the project + * extension directory, and later processes discover those artifacts normally. + * + * No browser/auth/provisioning happens here, so a test can drive the exact load + * path the CLI uses with a `buildTestHarness` fixture and temp dirs. + */ +export async function loadHarnessExtensions(args: { + harness: CuaAgentHarness; + session: Session; + cwd: string; + noExtensions: boolean; + agentDir?: string; + configuredPaths?: string[]; + initialScreenshot?: () => Promise; + selfExtend?: boolean; +}): Promise { + if (args.noExtensions) return undefined; + const agentDir = args.agentDir ?? getAgentDir(); + const configuredPaths = args.configuredPaths ?? [join(args.cwd, ".agents", "extensions")]; + const host = new HarnessExtensionHost({ + harness: args.harness, + session: args.session, + cwd: args.cwd, + configuredPaths, + agentDir, + initialScreenshot: args.initialScreenshot, + selfExtend: args.selfExtend, + }); + await host.load(); + return host; +} diff --git a/packages/cli/src/print.ts b/packages/cli/src/print.ts index 4027541..a9db257 100644 --- a/packages/cli/src/print.ts +++ b/packages/cli/src/print.ts @@ -93,12 +93,12 @@ export async function runPrint(opts: RunPrintOptions): Promise { } async function maybeInitialScreenshot(opts: RunPrintOptions): Promise { + // `skipInitialScreenshot` is decided once at startup (before extensions load), + // so an extension's startup message can't suppress the user's first-turn frame. if (opts.skipInitialScreenshot) return undefined; // Browser mode's only frame is the viewport; skip the OS-display capture // rather than mix coordinate frames on the first turn. if (opts.harness.getMode() === "browser") return undefined; - const hasPriorTurn = await sessionHasPriorTurn(opts.session); - if (hasPriorTurn) return undefined; const png = await captureScreenshot(opts.browserHandle.client, opts.browserHandle.browser.session_id); if (!png) return undefined; return [ @@ -109,13 +109,3 @@ async function maybeInitialScreenshot(opts: RunPrintOptions): Promise { - const entries = await session.getBranch(); - for (const entry of entries) { - if (entry.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) { - return true; - } - } - return false; -} diff --git a/packages/cli/src/tui/main.ts b/packages/cli/src/tui/main.ts index fffbf3c..ce92449 100644 --- a/packages/cli/src/tui/main.ts +++ b/packages/cli/src/tui/main.ts @@ -24,6 +24,7 @@ import { initTheme } from "@earendil-works/pi-coding-agent"; import { homedir } from "node:os"; import type { ImageContent, Model } from "@onkernel/cua-ai"; import { captureScreenshot, type CuaBrowserHandle } from "../harness-browser"; +import type { HarnessExtensions } from "../extensions/setup"; import { resolveCuaModelRef } from "../harness-models"; import { updateNamedSessionRuntime } from "../harness-named-sessions"; import type { ContextFile } from "../harness-skills"; @@ -61,6 +62,8 @@ export interface InteractiveOptions { namedSession?: string; /** Enable extra TUI render diagnostics for manual repros. */ debugTui?: boolean; + /** Loaded pi-extension host for /reload. Absent in fixture/headless and --no-extensions/untrusted paths, so /reload no-ops with a notice. */ + host?: HarnessExtensions; } /** @@ -318,6 +321,11 @@ export async function runInteractive(opts: InteractiveOptions): Promise await applyCompactCommand(opts, messages); return; } + if (parsed?.command === "reload") { + await applyReloadCommand(opts, messages); + requestRender("reload"); + return; + } if (parsed?.command === "skill") { const skill = (opts.skills ?? []).find((s) => s.name === parsed.name); if (!skill) { @@ -449,26 +457,17 @@ async function maybeInitialScreenshot( firstPromptSent: boolean, ): Promise { if (firstPromptSent) return undefined; + // `skipInitialScreenshot` is decided once at startup (before extensions load), + // so an extension's startup message can't suppress the user's first-turn frame. if (opts.skipInitialScreenshot) return undefined; // Browser mode's only frame is the viewport; skip the OS-display capture // rather than mix coordinate frames on the first turn. if (opts.harness.getMode() === "browser") return undefined; - if (await sessionHasPriorTurn(opts.session)) return undefined; const png = await captureScreenshot(opts.browserHandle.client, opts.browserHandle.browser.session_id); if (!png) return undefined; return [{ type: "image", data: png.toString("base64"), mimeType: "image/png" }]; } -async function sessionHasPriorTurn(session: Session): Promise { - const entries = await session.getBranch(); - for (const entry of entries) { - if (entry.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) { - return true; - } - } - return false; -} - async function applyModelCommand( opts: InteractiveOptions, footer: TelemetryFooter, @@ -567,6 +566,33 @@ async function applyCompactCommand(opts: InteractiveOptions, messages: MessageLi } } +export async function applyReloadCommand(opts: InteractiveOptions, messages: MessageList): Promise { + if (!opts.host) { + messages.addNotice("extensions are disabled"); + return; + } + messages.addNotice("reloading extensions…"); + try { + // reload() emits no harness event, so this helper is the only source of + // feedback; surface loadErrors so a broken edited extension isn't silently + // dropped with its tool missing. + const outcome = await opts.host.reload(); + if (outcome === "disposed" || opts.host.isDisposed()) { + // An extension calling ctx.shutdown() during the reload tears the host + // down; don't claim a successful reload. + messages.addNotice("session is shutting down; extensions were not reloaded"); + } else if (outcome === "coalesced") { + messages.addNotice("a reload is already in progress"); + } else if (opts.host.loadErrors.length > 0) { + for (const { path, error } of opts.host.loadErrors) messages.addError(`${path}: ${error}`); + } else { + messages.addNotice("extensions reloaded"); + } + } catch (err) { + messages.addError((err as Error).message); + } +} + async function refreshContextTokens(session: Session): Promise { const context = await session.buildContext(); return estimateContextTokens(context.messages).tokens; diff --git a/packages/cli/src/tui/slash-commands.ts b/packages/cli/src/tui/slash-commands.ts index fe83941..423ca44 100644 --- a/packages/cli/src/tui/slash-commands.ts +++ b/packages/cli/src/tui/slash-commands.ts @@ -8,8 +8,8 @@ import { listCuaModels } from "@onkernel/cua-ai"; /** * Build an autocomplete provider for the TUI editor with the slash commands - * the interactive app supports: `/model`, `/thinking`, `/compact`, plus a - * `/skill:` entry per loaded skill. + * the interactive app supports: `/model`, `/thinking`, `/compact`, `/reload`, + * plus a `/skill:` entry per loaded skill. * * Model and thinking values are exposed as `getArgumentCompletions` so * users can tab through CUA refs and reasoning levels. @@ -46,6 +46,11 @@ export function buildAutocompleteProvider( description: "Summarize older turns to free context budget", }); + commands.push({ + name: "reload", + description: "Reload pi extensions from disk", + }); + for (const skill of skills) { commands.push({ name: `skill:${skill.name}`, @@ -97,6 +102,7 @@ export type ParsedSlashCommand = | { command: "mode"; argument: string } | { command: "thinking"; argument: string } | { command: "compact"; argument: string } + | { command: "reload"; argument: string } | { command: "skill"; name: string; remainder: string }; /** @@ -111,11 +117,11 @@ export function parseSlashCommand(text: string): ParsedSlashCommand | undefined const [, name, rest] = skillMatch; return { command: "skill", name: name ?? "", remainder: (rest ?? "").trim() }; } - const builtinMatch = trimmed.match(/^\/(model|mode|thinking|compact)\s*(.*)$/); + const builtinMatch = trimmed.match(/^\/(model|mode|thinking|compact|reload)\s*(.*)$/); if (builtinMatch) { const [, name, rest] = builtinMatch; return { - command: name as "model" | "mode" | "thinking" | "compact", + command: name as "model" | "mode" | "thinking" | "compact" | "reload", argument: (rest ?? "").trim(), }; } diff --git a/packages/cli/test/add-tool.test.ts b/packages/cli/test/add-tool.test.ts new file mode 100644 index 0000000..5364b47 --- /dev/null +++ b/packages/cli/test/add-tool.test.ts @@ -0,0 +1,347 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { + existsSync, + mkdtempSync, + readFileSync, + readdirSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import { + renderToolExtension, + type AddToolInput, +} from "../src/extensions/add-tool"; +import { HarnessExtensionHost } from "../src/extensions/compat/host"; +import { loadHarnessExtensions } from "../src/extensions/setup"; +import { buildTestHarness, type TestHarnessFixture } from "./fixtures/harness"; + +const parameters = { + type: "object", + properties: {}, + additionalProperties: false, +}; + +function definition(name: string, text = "authored ok"): AddToolInput { + return { + name, + description: `${name} description`, + parameters, + execute: `async (_toolCallId, _params, _signal, _onUpdate) => ({ content: [{ type: "text", text: ${JSON.stringify(text)} }], details: {} })`, + }; +} + +let fx: TestHarnessFixture | undefined; +let host: HarnessExtensionHost | undefined; + +afterEach(async () => { + await host?.dispose(); + host = undefined; + fx = undefined; +}); + +async function setup( + options: { + selfExtend?: boolean; + extDir?: string; + turns?: Parameters[0]["turns"]; + } = {}, +) { + fx = await buildTestHarness({ + turns: options.turns ?? [{ steps: [{ type: "text", text: "ok" }] }], + }); + const extDir = options.extDir ?? mkdtempSync(join(tmpdir(), "cua-ext-")); + host = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + selfExtend: options.selfExtend ?? true, + }); + await host.load(); + return { fx, host, extDir }; +} + +function names(fixture: TestHarnessFixture): string[] { + return fixture.harness.getTools().map((tool) => tool.name); +} + +async function callAddTool(fixture: TestHarnessFixture, input: AddToolInput) { + const tool = fixture.harness + .getTools() + .find((candidate) => candidate.name === "add_tool"); + if (!tool) throw new Error("add_tool is not registered"); + return tool.execute("add-call", input, new AbortController().signal); +} + +describe("add_tool", () => { + it("is opt-in and remains the sole owner of its reserved name", async () => { + const enabled = await setup(); + expect( + names(enabled.fx).filter((name) => name === "add_tool"), + ).toHaveLength(1); + expect( + enabled.fx.harness.getActiveTools().map((tool) => tool.name), + ).toContain("add_tool"); + await host?.dispose(); + host = undefined; + + const disabledFx = await buildTestHarness({ + turns: [{ steps: [{ type: "text", text: "ok" }] }], + }); + const disabled = await loadHarnessExtensions({ + harness: disabledFx.harness, + session: disabledFx.session, + cwd: disabledFx.cwd, + noExtensions: false, + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + configuredPaths: [mkdtempSync(join(tmpdir(), "cua-ext-"))], + }); + expect( + disabledFx.harness.getTools().map((tool) => tool.name), + ).not.toContain("add_tool"); + await disabled?.dispose(); + }); + + it("drops a disk extension that collides with the loader", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync( + join(extDir, "collision.ts"), + renderToolExtension({ ...definition("add_tool"), label: "add_tool" }), + ); + const { fx: fixture, host: created } = await setup({ extDir }); + expect(names(fixture).filter((name) => name === "add_tool")).toHaveLength( + 1, + ); + expect(created.loadErrors.some((entry) => entry.path === "add_tool")).toBe( + true, + ); + }); + + it("persists and activates a tool", async () => { + const { fx: fixture, extDir } = await setup(); + const input = definition("sorted_tool"); + input.parameters = { + required: [], + properties: { z: { type: "string" }, a: { type: "number" } }, + type: "object", + }; + const result = await callAddTool(fixture, input); + const target = resolve(extDir, "sorted_tool.ts"); + expect(result.details).toEqual({ + written: target, + valid: true, + addedToolNames: ["sorted_tool"], + }); + expect(readFileSync(target, "utf8")).toBe( + renderToolExtension({ ...input, label: "sorted_tool" }), + ); + expect(names(fixture)).toContain("sorted_tool"); + }); + + it("activates and invokes the new tool before the run ends", async () => { + const input = definition("same_run_tool"); + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync( + join(extDir, "reduce-result.ts"), + `export default function (pi) { + pi.on("tool_result", (event) => event.toolName === "add_tool" + ? { content: [{ type: "text", text: "rewritten" }], details: { rewritten: true } } + : undefined); + }`, + ); + const { fx: fixture } = await setup({ + extDir, + turns: [ + { steps: [{ type: "tool_call", toolName: "add_tool", args: input }] }, + { steps: [{ type: "tool_call", toolName: "same_run_tool", args: {} }] }, + { steps: [{ type: "text", text: "done" }] }, + ], + }); + const trace: string[] = []; + fixture.harness.subscribe((event) => { + if (event.type === "agent_end") trace.push("agent_end"); + if ( + event.type === "tool_execution_end" && + event.toolName === "same_run_tool" && + !event.isError + ) + trace.push("same_run_tool"); + }); + await fixture.harness.prompt("add and use it"); + expect(trace).toEqual(["same_run_tool", "agent_end"]); + const secondContext = fixture.provider.contexts()[1]; + expect(secondContext.tools?.map((tool) => tool.name)).toContain( + "same_run_tool", + ); + const addResult = secondContext.messages.find( + (message) => + message.role === "toolResult" && message.toolName === "add_tool", + ); + expect(addResult?.addedToolNames).toEqual(["same_run_tool"]); + expect(addResult?.content).toEqual([ + { type: "text", text: "rewritten" }, + ]); + }); + + it("is discovered and callable in a new process", async () => { + const first = await setup(); + await callAddTool(first.fx, definition("durable_tool", "durable")); + await first.host.dispose(); + host = undefined; + + const script = ` + import { mkdtempSync } from "node:fs"; + import { tmpdir } from "node:os"; + import { join } from "node:path"; + import { HarnessExtensionHost } from "./packages/cli/src/extensions/compat/host.ts"; + import { buildTestHarness } from "./packages/cli/test/fixtures/harness.ts"; + const fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const host = new HarnessExtensionHost({ harness: fx.harness, session: fx.session, cwd: fx.cwd, configuredPaths: [process.env.EXT_DIR], agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")) }); + await host.load(); + const tool = fx.harness.getTools().find((item) => item.name === "durable_tool"); + if (!tool) throw new Error("durable tool was not discovered"); + const result = await tool.execute("call", {}, new AbortController().signal); + console.log(result.content[0].text); + await host.dispose(); + `; + const child = spawnSync( + process.execPath, + ["--import", "tsx", "--input-type=module", "--eval", script], + { + cwd: resolve(import.meta.dirname, "../../.."), + env: { ...process.env, EXT_DIR: first.extDir }, + encoding: "utf8", + }, + ); + expect(child.stderr).toBe(""); + expect(child.status).toBe(0); + expect(child.stdout).toContain("durable"); + }); + + it.each([ + ["../escape", "path-like name"], + ["bad/name", "slash"], + ["bad\\name", "backslash"], + ["_leading", "non-letter prefix"], + ["a".repeat(65), "overlength"], + ])("rejects unsafe name %s (%s) without writing", async (name) => { + const { fx: fixture, extDir } = await setup(); + await expect(callAddTool(fixture, definition(name))).rejects.toThrow( + /name must/, + ); + expect(readdirSync(extDir)).toEqual([]); + }); + + it("rejects invalid schemas and execute sources without durable or live mutation", async () => { + const { fx: fixture, extDir } = await setup(); + const badSchema = definition("bad_schema"); + badSchema.parameters = { type: "string" }; + await expect(callAddTool(fixture, badSchema)).rejects.toThrow( + /top-level type/, + ); + const badExecute = definition("bad_execute"); + badExecute.execute = "() => ({ content: [], details: {} })"; + await expect(callAddTool(fixture, badExecute)).rejects.toThrow( + /async function expression/, + ); + const brokenExecute = definition("broken_execute"); + brokenExecute.execute = "async ( => ({ content: [], details: {} })"; + await expect(callAddTool(fixture, brokenExecute)).rejects.toThrow( + /validation failed/, + ); + const sequenceExecute = definition("sequence_execute"); + sequenceExecute.execute = "async () => ({ content: [], details: {} }), 42"; + await expect(callAddTool(fixture, sequenceExecute)).rejects.toThrow( + /one async function expression/, + ); + expect(readdirSync(extDir)).toEqual([]); + expect(names(fixture)).not.toEqual( + expect.arrayContaining([ + "bad_schema", + "bad_execute", + "broken_execute", + "sequence_execute", + ]), + ); + }); + + it("returns a failed tool result without ending the run", async () => { + const { fx: fixture } = await setup({ + turns: [ + { + steps: [ + { + type: "tool_call", + toolName: "add_tool", + args: definition("../invalid"), + }, + ], + }, + { steps: [{ type: "text", text: "continued" }] }, + ], + }); + await fixture.harness.prompt("try an invalid tool and continue"); + const failed = fixture.provider.contexts()[1].messages.find( + (message) => + message.role === "toolResult" && message.toolName === "add_tool", + ); + expect(failed?.isError).toBe(true); + expect(failed?.addedToolNames).toBeUndefined(); + }); + + it("rejects existing artifacts and tool-name collisions without overwriting", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + const target = join(extDir, "occupied.ts"); + writeFileSync(target, "do not replace"); + const { fx: fixture } = await setup({ extDir }); + await expect( + callAddTool(fixture, definition("occupied")), + ).rejects.toThrow(); + expect(readFileSync(target, "utf8")).toBe("do not replace"); + await expect(callAddTool(fixture, definition("add_tool"))).rejects.toThrow( + /already exists/, + ); + }); + + it("keeps current base and extension tools during a model-switch race", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync( + join(extDir, "disk.ts"), + renderToolExtension({ ...definition("disk_tool"), label: "disk_tool" }), + ); + const { fx: fixture } = await setup({ extDir }); + await Promise.all([ + callAddTool(fixture, definition("runtime_tool")), + fixture.harness.setModel("anthropic:claude-opus-4-7"), + ]); + expect(names(fixture)).toEqual( + expect.arrayContaining(["add_tool", "disk_tool", "runtime_tool"]), + ); + expect(fixture.harness.getModel().provider).toBe("anthropic"); + }); + + it("manual reload adopts the durable artifact and observes edits and removal", async () => { + const { fx: fixture, extDir, host: created } = await setup(); + await callAddTool(fixture, definition("reloadable", "first")); + const target = join(extDir, "reloadable.ts"); + writeFileSync( + target, + renderToolExtension({ + ...definition("reloadable", "second"), + label: "reloadable", + }), + ); + expect(await created.reload()).toBe("reloaded"); + expect(names(fixture)).toEqual( + expect.arrayContaining(["add_tool", "reloadable"]), + ); + await import("node:fs/promises").then(({ rm }) => rm(target)); + expect(await created.reload()).toBe("reloaded"); + expect(names(fixture)).toContain("add_tool"); + expect(names(fixture)).not.toContain("reloadable"); + expect(existsSync(target)).toBe(false); + }); +}); diff --git a/packages/cli/test/extension-loader.test.ts b/packages/cli/test/extension-loader.test.ts new file mode 100644 index 0000000..1cbb24e --- /dev/null +++ b/packages/cli/test/extension-loader.test.ts @@ -0,0 +1,117 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { randomUUID } from "node:crypto"; +import { + loadHarnessExtensions, + type HarnessExtensions, +} from "../src/extensions/setup"; +import { buildTestHarness, type TestHarnessFixture } from "./fixtures/harness"; + +let fx: TestHarnessFixture | undefined; +let host: HarnessExtensions | undefined; + +afterEach(async () => { + await host?.dispose(); + host = undefined; + fx = undefined; +}); + +/** A minimal, import-free extension that registers a single named tool. */ +function makeToolExtension(toolName: string): string { + return [ + "export default function (pi) {", + " pi.registerTool({", + ` name: ${JSON.stringify(toolName)},`, + ` label: ${JSON.stringify(toolName)},`, + ` description: ${JSON.stringify(toolName)},`, + ' parameters: { type: "object", properties: {}, additionalProperties: false },', + ' async execute() { return { content: [{ type: "text", text: "ok" }], details: {} }; },', + " });", + "}", + "", + ].join("\n"); +} + +function tempAgentDir(): string { + return mkdtempSync(join(tmpdir(), "cua-agentdir-")); +} + +describe("loadHarnessExtensions", () => { + it("loads a configured extension onto the harness", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "probe.ts"), makeToolExtension("loader_probe")); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: false, + agentDir: tempAgentDir(), + configuredPaths: [extDir], + }); + + expect(host).toBeDefined(); + expect(fx.harness.getTools().map((t) => t.name)).toContain("loader_probe"); + }); + + it("returns undefined when extensions are disabled", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "probe.ts"), makeToolExtension("loader_probe")); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: true, + agentDir: tempAgentDir(), + configuredPaths: [extDir], + }); + + expect(host).toBeUndefined(); + expect(fx.harness.getTools().map((t) => t.name)).not.toContain("loader_probe"); + }); + + it("loads the implicit project /.pi/extensions scan by default", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + // Unique per run so the whole-harness tool assertion can't collide with + // another worker registering the same name under pool concurrency. + const probe = `pi_probe_${randomUUID().replace(/-/g, "")}`; + const projectExtDir = join(fx.cwd, ".pi", "extensions"); + mkdirSync(projectExtDir, { recursive: true }); + writeFileSync(join(projectExtDir, "probe.ts"), makeToolExtension(probe)); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: false, + agentDir: tempAgentDir(), + }); + + expect(host).toBeDefined(); + expect(fx.harness.getTools().map((t) => t.name)).toContain(probe); + }); + + it("loads project /.agents/extensions by default", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const probe = `agents_probe_${randomUUID().replace(/-/g, "")}`; + const projectExtDir = join(fx.cwd, ".agents", "extensions"); + mkdirSync(projectExtDir, { recursive: true }); + writeFileSync(join(projectExtDir, "probe.ts"), makeToolExtension(probe)); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: false, + agentDir: tempAgentDir(), + }); + + expect(host).toBeDefined(); + expect(fx.harness.getTools().map((t) => t.name)).toContain(probe); + }); +}); diff --git a/packages/cli/test/extension-reload.test.ts b/packages/cli/test/extension-reload.test.ts new file mode 100644 index 0000000..abf6f79 --- /dev/null +++ b/packages/cli/test/extension-reload.test.ts @@ -0,0 +1,84 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + loadHarnessExtensions, + type HarnessExtensions, +} from "../src/extensions/setup"; +import { buildTestHarness, type TestHarnessFixture } from "./fixtures/harness"; + +let fx: TestHarnessFixture | undefined; +let host: HarnessExtensions | undefined; + +afterEach(async () => { + await host?.dispose(); + host = undefined; + fx = undefined; +}); + +/** A minimal, import-free extension that registers a single named tool. */ +function makeToolExtension(toolName: string): string { + return [ + "export default function (pi) {", + " pi.registerTool({", + ` name: ${JSON.stringify(toolName)},`, + ` label: ${JSON.stringify(toolName)},`, + ` description: ${JSON.stringify(toolName)},`, + ' parameters: { type: "object", properties: {}, additionalProperties: false },', + ' async execute() { return { content: [{ type: "text", text: "ok" }], details: {} }; },', + " });", + "}", + "", + ].join("\n"); +} + +describe("/reload path via the real host", () => { + it("hot-swaps an edited extension and reports a clean reload", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + const extFile = join(extDir, "learned.ts"); + writeFileSync(extFile, makeToolExtension("alpha_tool")); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: false, + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + configuredPaths: [extDir], + }); + expect(host).toBeDefined(); + expect(fx.harness.getTools().map((t) => t.name)).toContain("alpha_tool"); + + writeFileSync(extFile, makeToolExtension("beta_tool")); + await host!.reload(); + + const toolNames = fx.harness.getTools().map((t) => t.name); + expect(toolNames).toContain("beta_tool"); + expect(toolNames).not.toContain("alpha_tool"); + expect(host!.loadErrors).toHaveLength(0); + }); + + it("surfaces loadErrors when an edited extension is broken", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + const extFile = join(extDir, "learned.ts"); + writeFileSync(extFile, makeToolExtension("alpha_tool")); + + host = await loadHarnessExtensions({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + noExtensions: false, + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + configuredPaths: [extDir], + }); + expect(host).toBeDefined(); + + writeFileSync(extFile, "export default function ( { // syntactically broken\n"); + await host!.reload(); + + expect(host!.loadErrors.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/cli/test/extensions.test.ts b/packages/cli/test/extensions.test.ts new file mode 100644 index 0000000..8756dac --- /dev/null +++ b/packages/cli/test/extensions.test.ts @@ -0,0 +1,374 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { cpSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { HarnessExtensionHost } from "../src/extensions/compat/host"; +import { buildTestHarness, type TestHarnessFixture } from "./fixtures/harness"; + +const here = dirname(fileURLToPath(import.meta.url)); +const EXAMPLE_EXTENSION = join(here, "fixtures", "extensions", "click-visual.ts"); + +let fx: TestHarnessFixture | undefined; +let host: HarnessExtensionHost | undefined; + +afterEach(async () => { + await host?.dispose(); + host = undefined; + fx = undefined; +}); + +/** Copy the committed example extension into an isolated discovery directory. */ +function makeExtensionDir(): string { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + cpSync(EXAMPLE_EXTENSION, join(extDir, "click-visual.ts")); + return extDir; +} + +async function loadHost(): Promise { + fx = await buildTestHarness({ + turns: [ + { steps: [{ type: "tool_call", toolName: "click_visual", args: { description: "the button" } }] }, + { steps: [{ type: "text", text: "done" }] }, + ], + }); + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [makeExtensionDir()], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + await created.load(); + host = created; + return created; +} + +describe("HarnessExtensionHost", () => { + it("registers an extension tool on the harness", async () => { + await loadHost(); + const toolNames = fx!.harness.getTools().map((tool) => tool.name); + expect(toolNames).toContain("click_visual"); + }); + + it("bridges harness events into extension handlers", async () => { + await loadHost(); + + let sawAgentStart = false; + let toolResultText = ""; + fx!.harness.subscribe((event) => { + if (event.type === "agent_start") sawAgentStart = true; + if (event.type === "tool_execution_end" && event.toolName === "click_visual") { + const content = (event.result as { content: Array<{ type: string; text?: string }> }).content; + toolResultText = content.map((part) => part.text ?? "").join(""); + } + }); + + await fx!.harness.prompt("hi"); + + // agent_start reached the harness subscriber (loop-event forwarding path)… + expect(sawAgentStart).toBe(true); + // …and the extension's own agent_start handler ran: the tool, executed via + // the runner's wrapper, reports the count the extension incremented. + expect(toolResultText).toContain("would click: the button"); + expect(toolResultText).toContain("runs=1"); + }); + + it("keeps the extension tool after a model switch", async () => { + await loadHost(); + expect(fx!.harness.getTools().map((tool) => tool.name)).toContain("click_visual"); + + await fx!.harness.setModel("anthropic:claude-opus-4-7"); + + expect(fx!.harness.getTools().map((tool) => tool.name)).toContain("click_visual"); + }); + + it("keeps the extension tool active across a model switch", async () => { + await loadHost(); + expect(fx!.harness.getActiveTools().map((tool) => tool.name)).toContain("click_visual"); + + // setModel rebuilds the harness tool list from construction-time tools and + // resets active state; the host must re-activate extension tools, not just + // re-register them. + await fx!.harness.setModel("anthropic:claude-opus-4-7"); + + expect(fx!.harness.getActiveTools().map((tool) => tool.name)).toContain("click_visual"); + }); + + it("re-registers extension tools after reload", async () => { + const created = await loadHost(); + expect(fx!.harness.getTools().map((tool) => tool.name)).toContain("click_visual"); + + await created.reload(); + + expect(fx!.harness.getTools().map((tool) => tool.name)).toContain("click_visual"); + }); + + it("drops a renamed extension's old tool on reload", async () => { + // Model a meta-agent revising a learned tool: the extension file is rewritten + // to register a differently-named tool, and reload must reflect the on-disk + // state — the previous tool should not linger on the harness. + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + const extFile = join(extDir, "learned.ts"); + writeFileSync(extFile, makeToolExtension("alpha_tool")); + + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + host = created; + await created.load(); + expect(fx.harness.getTools().map((tool) => tool.name)).toContain("alpha_tool"); + + writeFileSync(extFile, makeToolExtension("beta_tool")); + await created.reload(); + + const toolNames = fx.harness.getTools().map((tool) => tool.name); + expect(toolNames).toContain("beta_tool"); + expect(toolNames).not.toContain("alpha_tool"); + }); + + it("throws when load() is called after dispose", async () => { + const created = await loadHost(); + await created.dispose(); + await expect(created.load()).rejects.toThrow(/disposed/); + }); + + it("ignores a second load() rather than stacking a duplicate registration", async () => { + const created = await loadHost(); + const count = () => + fx!.harness.getTools().map((tool) => tool.name).filter((name) => name === "click_visual").length; + expect(count()).toBe(1); + await created.load(); + expect(count()).toBe(1); + }); + + it("removes its tools from the harness on dispose", async () => { + const created = await loadHost(); + expect(fx!.harness.getTools().map((tool) => tool.name)).toContain("click_visual"); + // dispose (e.g. via an extension's ctx.shutdown) must not leave the tool + // registered+active once its runner binding is gone. + await created.dispose(); + const names = fx!.harness.getTools().map((tool) => tool.name); + expect(names).not.toContain("click_visual"); + }); + + it("coalesces a reload requested while another is in flight", async () => { + const created = await loadHost(); + // A second request must report that the first reload still owns the operation. + const first = created.reload(); + const second = await created.reload(); + expect(second).toBe("coalesced"); + expect(await first).toBe("reloaded"); + }); + + it("invalidates extension APIs after reload and disposal", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync( + join(extDir, "capture.ts"), + "export default function (pi) { (globalThis.__capturedExtensionApis ??= []).push(pi); }", + ); + const captured = [] as Array<{ getActiveTools(): string[] }>; + (globalThis as typeof globalThis & { __capturedExtensionApis?: typeof captured }).__capturedExtensionApis = captured; + fx = await buildTestHarness({ + turns: [{ steps: [{ type: "text", text: "ok" }] }], + }); + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + host = created; + await created.load(); + await created.reload(); + expect(() => captured[0].getActiveTools()).toThrow(/stale/); + await created.dispose(); + expect(() => captured[1].getActiveTools()).toThrow(/stale/); + delete (globalThis as typeof globalThis & { __capturedExtensionApis?: typeof captured }).__capturedExtensionApis; + }); + + it("finishes an in-flight reload before disposal", async () => { + const created = await loadHost(); + const originalWaitForIdle = fx!.harness.waitForIdle.bind(fx!.harness); + let release!: () => void; + const gate = new Promise((resolve) => { + release = resolve; + }); + fx!.harness.waitForIdle = async () => { + await gate; + await originalWaitForIdle(); + }; + const reload = created.reload(); + const disposal = created.dispose(); + release(); + expect(await reload).toBe("disposed"); + await disposal; + expect(created.isDisposed()).toBe(true); + expect(fx!.harness.getTools().map((tool) => tool.name)).not.toContain( + "click_visual", + ); + }); + + it("publishes a dynamically registered extension tool before the next turn", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "dynamic.ts"), DYNAMIC_TOOL_EXTENSION); + fx = await buildTestHarness({ + turns: [ + { steps: [{ type: "tool_call", toolName: "register_later", args: {} }] }, + { steps: [{ type: "tool_call", toolName: "registered_later", args: {} }] }, + { steps: [{ type: "text", text: "done" }] }, + ], + }); + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + host = created; + await created.load(); + await fx.harness.prompt("register and call it"); + const secondContext = fx.provider.contexts()[1]; + expect(secondContext.tools?.map((tool) => tool.name)).toContain( + "registered_later", + ); + const marker = secondContext.messages.find( + (message) => + message.role === "toolResult" && + message.toolName === "register_later", + ); + expect(marker?.addedToolNames).toEqual(["registered_later"]); + }); + + it("does not let a startup extension message consume the first-turn screenshot", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "startup-msg.ts"), SEND_ON_STARTUP_EXTENSION); + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + let screenshotCalls = 0; + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + initialScreenshot: async () => { + screenshotCalls += 1; + return [{ type: "image", data: "x", mimeType: "image/png" }]; + }, + }); + host = created; + await created.load(); + // Let the fire-and-forget startup prompt settle so an unguarded capture would + // have happened by now. + await new Promise((resolve) => setTimeout(resolve, 20)); + // The extension's startup sendUserMessage must not have captured the + // first-turn screenshot — that belongs to the user's real first prompt. + expect(screenshotCalls).toBe(0); + }); + + it("captures a failing extension sendUserMessage instead of an unhandled rejection", async () => { + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "startup-msg.ts"), SEND_ON_STARTUP_EXTENSION); + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + // Force the extension-initiated prompt to reject (models a busy/concurrent + // harness); without a catch this would surface as an unhandled rejection. + const realPrompt = fx.harness.prompt.bind(fx.harness); + fx.harness.prompt = ((text: string, options?: unknown) => + typeof text === "string" && text.includes("ext-startup-msg") + ? Promise.reject(new Error("harness busy")) + : realPrompt(text, options as never)) as typeof fx.harness.prompt; + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + host = created; + await created.load(); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(created.loadErrors.some((e) => e.path === "")).toBe(true); + }); + + it("drops an extension tool that shadows a built-in and keeps the built-in", async () => { + fx = await buildTestHarness({ turns: [{ steps: [{ type: "text", text: "ok" }] }] }); + const builtin = fx.harness.getTools()[0].name; + const extDir = mkdtempSync(join(tmpdir(), "cua-ext-")); + writeFileSync(join(extDir, "shadow.ts"), makeToolExtension(builtin)); + const created = new HarnessExtensionHost({ + harness: fx.harness, + session: fx.session, + cwd: fx.cwd, + configuredPaths: [extDir], + agentDir: mkdtempSync(join(tmpdir(), "cua-agentdir-")), + }); + host = created; + // An extension may not shadow a built-in: the shadow would vanish the + // built-in when the extension is later removed on reload. + await created.load(); + const names = fx.harness.getTools().map((tool) => tool.name); + expect(names.filter((name) => name === builtin)).toHaveLength(1); + expect(created.loadErrors.some((e) => e.path === builtin && /built-in/.test(e.error))).toBe(true); + }); +}); + +const DYNAMIC_TOOL_EXTENSION = ` + export default function (pi) { + pi.registerTool({ + name: "register_later", + label: "register later", + description: "register another tool", + parameters: { type: "object", properties: {} }, + async execute() { + pi.registerTool({ + name: "registered_later", + label: "registered later", + description: "registered at runtime", + parameters: { type: "object", properties: {} }, + async execute() { return { content: [{ type: "text", text: "ok" }], details: {} }; }, + }); + return { content: [{ type: "text", text: "registered" }], details: {} }; + }, + }); + } +`; + +/** An extension that sends a user message during the startup session_start. */ +const SEND_ON_STARTUP_EXTENSION = [ + "export default function (pi) {", + ' pi.on("session_start", (event) => {', + ' if (event.reason === "startup") pi.sendUserMessage("ext-startup-msg");', + " });", + " pi.registerTool({", + ' name: "startup_probe",', + ' label: "startup probe",', + ' description: "noop",', + ' parameters: { type: "object", properties: {}, additionalProperties: false },', + ' async execute() { return { content: [{ type: "text", text: "ok" }], details: {} }; },', + " });", + "}", + "", +].join("\n"); + +/** A minimal, import-free extension that registers a single named tool. */ +function makeToolExtension(toolName: string): string { + return [ + "export default function (pi) {", + " pi.registerTool({", + ` name: ${JSON.stringify(toolName)},`, + ` label: ${JSON.stringify(toolName)},`, + ` description: ${JSON.stringify(toolName)},`, + ' parameters: { type: "object", properties: {}, additionalProperties: false },', + ' async execute() { return { content: [{ type: "text", text: "ok" }], details: {} }; },', + " });", + "}", + "", + ].join("\n"); +} diff --git a/packages/cli/test/fixtures/extensions/click-visual.ts b/packages/cli/test/fixtures/extensions/click-visual.ts new file mode 100644 index 0000000..e32061a --- /dev/null +++ b/packages/cli/test/fixtures/extensions/click-visual.ts @@ -0,0 +1,47 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +/** + * Example pi extension exercising both Tier-A primitives the host proves: + * event subscription (`pi.on`) and tool registration (`pi.registerTool`). + * + * Parameters are declared as a plain JSON Schema object so the extension has no + * runtime imports: it is loaded from an arbitrary directory by the jiti loader, + * which resolves imports relative to that directory, not the cua workspace. + * + * Safe headless: the `agent_end` notification is guarded by `ctx.hasUI`, which + * is false under the host's no-op UI context, so it never runs in print mode. + * The `click_visual` tool reports the bridged `agent_start` count so a test can + * observe that harness events reach extension handlers. + */ +export default function clickVisual(pi: ExtensionAPI): void { + let agentRuns = 0; + + pi.on("agent_start", () => { + agentRuns += 1; + }); + + pi.on("agent_end", (_event, ctx) => { + if (ctx.hasUI) ctx.ui.notify(`agent runs: ${agentRuns}`, "info"); + }); + + pi.registerTool({ + name: "click_visual", + label: "Click (visual)", + description: "Stub: click an on-screen element by visual description.", + parameters: { + type: "object", + properties: { + description: { type: "string", description: "What to click" }, + }, + required: ["description"], + additionalProperties: false, + }, + async execute(_toolCallId, params) { + const description = (params as { description: string }).description; + return { + content: [{ type: "text", text: `would click: ${description} (runs=${agentRuns})` }], + details: { agentRuns }, + }; + }, + }); +} diff --git a/packages/cli/test/fixtures/scripted-provider.ts b/packages/cli/test/fixtures/scripted-provider.ts index 601b2d7..1e33a4c 100644 --- a/packages/cli/test/fixtures/scripted-provider.ts +++ b/packages/cli/test/fixtures/scripted-provider.ts @@ -34,6 +34,8 @@ export interface ScriptedProviderHandle { callCount(): number; /** Latest context the provider was called with (assistant-side mock). */ lastContext(): Context | undefined; + /** Every provider context in dispatch order. */ + contexts(): readonly Context[]; /** Latest stream options the provider was called with. */ lastStreamOptions(): CuaSimpleStreamOptions | undefined; } @@ -48,10 +50,12 @@ export function createScriptedCuaModels(providerId: string, turns: ScriptedTurn[ const state = { index: 0, lastContext: undefined as Context | undefined, + contexts: [] as Context[], lastStreamOptions: undefined as CuaSimpleStreamOptions | undefined, }; const dispatch = (model: Model, context: Context, options?: CuaSimpleStreamOptions) => { state.lastContext = context; + state.contexts.push(context); state.lastStreamOptions = options; const turn = turns[state.index]; state.index += 1; @@ -71,6 +75,7 @@ export function createScriptedCuaModels(providerId: string, turns: ScriptedTurn[ reset(): void { state.index = 0; state.lastContext = undefined; + state.contexts = []; state.lastStreamOptions = undefined; }, callCount(): number { @@ -79,6 +84,9 @@ export function createScriptedCuaModels(providerId: string, turns: ScriptedTurn[ lastContext(): Context | undefined { return state.lastContext; }, + contexts(): readonly Context[] { + return state.contexts; + }, lastStreamOptions(): CuaSimpleStreamOptions | undefined { return state.lastStreamOptions; }, diff --git a/packages/cli/test/reload-command.test.ts b/packages/cli/test/reload-command.test.ts new file mode 100644 index 0000000..2f79b2d --- /dev/null +++ b/packages/cli/test/reload-command.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it, vi } from "vitest"; +import { initTheme } from "@earendil-works/pi-coding-agent"; +import { applyReloadCommand, type InteractiveOptions } from "../src/tui/main"; +import { MessageList } from "../src/tui/message-list"; +import type { HarnessExtensions } from "../src/extensions/setup"; + +// `applyReloadCommand` is the glue the `/reload` dispatch runs (tui/main.ts): +// it bridges the parsed command to `host.reload()` and reports the outcome. +// MessageList colorizes via pi's theme singleton, so init it once. +initTheme(); + +/** Build the `applyReloadCommand` arg pair with a spyable host and message log. */ +function setup(host: HarnessExtensions | undefined): { + opts: InteractiveOptions; + messages: MessageList; + notices: string[]; + errors: string[]; +} { + const messages = new MessageList(); + const notices: string[] = []; + const errors: string[] = []; + vi.spyOn(messages, "addNotice").mockImplementation((text) => void notices.push(text)); + vi.spyOn(messages, "addError").mockImplementation((text) => void errors.push(text)); + return { opts: { host } as InteractiveOptions, messages, notices, errors }; +} + +describe("applyReloadCommand (/reload glue)", () => { + it("invokes host.reload() and reports a clean reload", async () => { + const reload = vi.fn(async () => {}); + const host = { reload, loadErrors: [], isDisposed: () => false } as unknown as HarnessExtensions; + const { opts, messages, notices, errors } = setup(host); + + await applyReloadCommand(opts, messages); + + expect(reload).toHaveBeenCalledTimes(1); + expect(notices).toContain("extensions reloaded"); + expect(errors).toHaveLength(0); + }); + + it("surfaces loadErrors after reload", async () => { + const reload = vi.fn(async () => {}); + const host = { + reload, + loadErrors: [{ path: "/ext/broken.ts", error: "boom" }], + isDisposed: () => false, + } as unknown as HarnessExtensions; + const { opts, messages, errors, notices } = setup(host); + + await applyReloadCommand(opts, messages); + + expect(reload).toHaveBeenCalledTimes(1); + expect(errors).toContain("/ext/broken.ts: boom"); + expect(notices).not.toContain("extensions reloaded"); + }); + + it("reports an in-progress reload instead of claiming success", async () => { + // A second manual reload coalesces with the operation already in flight; + // the command must not print "extensions reloaded" for work it + // didn't actually perform. + const reload = vi.fn(async () => "coalesced" as const); + const host = { reload, loadErrors: [], isDisposed: () => false } as unknown as HarnessExtensions; + const { opts, messages, notices } = setup(host); + + await applyReloadCommand(opts, messages); + + expect(notices.some((n) => n.includes("already in progress"))).toBe(true); + expect(notices).not.toContain("extensions reloaded"); + }); + + it("does not report success when the host disposed during reload", async () => { + const reload = vi.fn(async () => {}); + const host = { reload, loadErrors: [], isDisposed: () => true } as unknown as HarnessExtensions; + const { opts, messages, notices } = setup(host); + + await applyReloadCommand(opts, messages); + + expect(notices).not.toContain("extensions reloaded"); + expect(notices.some((n) => n.includes("shutting down"))).toBe(true); + }); + + it("no-ops with a notice when no host is loaded", async () => { + const { opts, messages, notices } = setup(undefined); + + await applyReloadCommand(opts, messages); + + expect(notices).toContain("extensions are disabled"); + }); +}); diff --git a/packages/cli/test/slash-commands.test.ts b/packages/cli/test/slash-commands.test.ts index 84caa1a..5d3152d 100644 --- a/packages/cli/test/slash-commands.test.ts +++ b/packages/cli/test/slash-commands.test.ts @@ -26,6 +26,13 @@ describe("parseSlashCommand", () => { expect(parseSlashCommand("/compact")).toEqual({ command: "compact", argument: "" }); }); + it("parses /reload", () => { + expect(parseSlashCommand("/reload")).toEqual({ command: "reload", argument: "" }); + // Matches the existing builtin family: the bare command consumes any + // trailing text into `argument` (which /reload ignores), same as /compact. + expect(parseSlashCommand("/reload now")).toEqual({ command: "reload", argument: "now" }); + }); + it("parses /skill: with optional remainder", () => { expect(parseSlashCommand("/skill:hello")).toEqual({ command: "skill", diff --git a/vendor/pi/README.md b/vendor/pi/README.md new file mode 100644 index 0000000..0cad6a6 --- /dev/null +++ b/vendor/pi/README.md @@ -0,0 +1,20 @@ +# Pi message-anchored tool packages + +These four tarballs are built from upstream pi-mono commit +`3d8f74357c169d24f996a1611ecc4be72b7744bd`, the first commit containing +message-anchored dynamic tool loading. npm had not published a release containing +that commit when this change was implemented. + +All package versions and internal `@earendil-works/*` dependencies were rewritten +to the shared prerelease version `0.80.7-anchor.3d8f743` before running the normal +upstream build and `npm pack`. Replace these anchored dependencies with the first +normal npm release containing the commit before merging when one is available. +The package manifests use immutable raw GitHub URLs pinned to the commit that +first checked in these tarballs so packed CUA packages remain installable. + +SHA-256: + +- `earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz`: `04a7e2f330b56b6352d3a72b1b8785f8549dd68a985ca84c145bd4b1a8456f38` +- `earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz`: `d1ba7fce6f448ab4bd16baddfb4ae3d3d7f07c77b2f014ab63588aa4b9c09c1d` +- `earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz`: `9af582b60b08fdab90d9b2c9bf8040ae410131e6785c77d844f9b87aeb714600` +- `earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz`: `a8ffd52318c8af5674c70f176489fe44117e2ea267f7b0712bb538a1f35450a9` diff --git a/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz b/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz new file mode 100644 index 0000000..1c306d8 Binary files /dev/null and b/vendor/pi/earendil-works-pi-agent-core-0.80.7-anchor.3d8f743.tgz differ diff --git a/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz b/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz new file mode 100644 index 0000000..4fd056d Binary files /dev/null and b/vendor/pi/earendil-works-pi-ai-0.80.7-anchor.3d8f743.tgz differ diff --git a/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz b/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz new file mode 100644 index 0000000..0c63dd0 Binary files /dev/null and b/vendor/pi/earendil-works-pi-coding-agent-0.80.7-anchor.3d8f743.tgz differ diff --git a/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz b/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz new file mode 100644 index 0000000..ae80106 Binary files /dev/null and b/vendor/pi/earendil-works-pi-tui-0.80.7-anchor.3d8f743.tgz differ