diff --git a/docs/agent-tool-configuration-spec.md b/docs/agent-tool-configuration-spec.md index 62a76535..93ca071b 100644 --- a/docs/agent-tool-configuration-spec.md +++ b/docs/agent-tool-configuration-spec.md @@ -138,7 +138,6 @@ const agent = new CuaAgent({ const harness = new CuaAgentHarness({ browser, client, - env, session, model: "openai:gpt-5.6-sol", tools: [ @@ -603,7 +602,7 @@ Every provider tool surface must expose the first-party source it mirrors. CUA-a 2. **Payload transforms:** transforms consume stable identities, declare static write claims, and compose in a fixed phase order. 3. **Result ownership:** each tool returns only requested reads, explicit screenshots, or its own structured semantic feedback. 4. **Batch overlap:** batches are mechanical; `browser_act` remains semantic; browser batches share ref state without a workflow DSL. -5. **Dynamic loading:** `setTools()` uses pi 0.80.10 additive markers only for final, cache-preserving in-tool additions; other changes are eager. +5. **Dynamic loading:** `setTools()` uses pi 0.83.0 additive markers only for final, cache-preserving in-tool additions; other changes are eager. 6. **Shared resources:** one resource pool survives tool/model changes and owns the translator and lazy CDP executor. 7. **Provider exports:** the native OpenAI, Anthropic, Google, Tzafon, and Yutori surfaces are namespaced, cite first-party sources, and are tested against their declared contracts. Meta, xAI, and Moonshot use CUA-authored browser tools; the CLI explicitly appends `browser_act` to the Meta and xAI catalogs. Moonshot is excluded: its API accepts the complex `browser_wait_for` schema but rejects a request carrying `browser_act`'s much larger one, so the catalog gates oversized schemas separately from merely-complex ones. diff --git a/package-lock.json b/package-lock.json index fc54f51b..0f50d152 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,17 +118,17 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.975.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", - "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", + "version": "3.977.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.5.tgz", + "integrity": "sha512-O5otOc1c6UZh5HsHAaPdYBcUUR9HL6mtnKqvc8nxN/CKDGUBUpsdh0q8K04Uz/dd1i0TaGyIQuQNqoO7+ad2TQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@aws-sdk/xml-builder": "^3.972.34", + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.29.2", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", + "@smithy/core": "^3.31.1", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -137,15 +137,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "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==", + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.66.tgz", + "integrity": "sha512-bOzP2+zdJ0XrghywB4FaJXtGZCx9yS0AGps+VJ5yEgg30wVyHNmVDBwVDXcRypzQY5iLGCS3NSn0nsuISqjFCQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -153,17 +153,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.59", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", - "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", + "version": "3.972.68", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.68.tgz", + "integrity": "sha512-lkunS8X+H6V76WE+t/uGQm/U8v0JXK5mLfNFTUAMlE1kqaCjwlmqKJrgCVtqjK/vqnlrSWsLK4Lr4NANBWlfTQ==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -171,12 +171,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "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==", + "version": "4.9.13", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.13.tgz", + "integrity": "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.3", + "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, @@ -185,23 +185,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "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==", + "version": "3.973.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.11.tgz", + "integrity": "sha512-KoDEolYtLHG/8C+IiZpXbJWyBOMkrHV+j66Kb9PBXmLv5euGb7aELvuCmLenoGAV6gBW2wM7TsG/1e5iulH4kA==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/credential-provider-env": "^3.972.66", + "@aws-sdk/credential-provider-http": "^3.972.68", + "@aws-sdk/credential-provider-login": "^3.972.73", + "@aws-sdk/credential-provider-process": "^3.972.66", + "@aws-sdk/credential-provider-sso": "^3.973.10", + "@aws-sdk/credential-provider-web-identity": "^3.972.72", + "@aws-sdk/nested-clients": "^3.997.40", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -209,16 +209,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "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==", + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.73.tgz", + "integrity": "sha512-tjsxMkTAFkmiV9ycmymapb9nLECWVOwFs0bZMQ9gB9bnbY8/HwfukHZlWbXZZp7qkPU6EXAfOcMm3DioFFEywA==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/nested-clients": "^3.997.40", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -226,21 +226,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.67", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.67.tgz", - "integrity": "sha512-oYlzWst56rlhhjbYnexwv5hVLYe1cW4liLObhDfxDLI4RAQzleMVHQgQgx7XsC4HKj4e3kjT8v9DId+Pi/dndw==", + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.77.tgz", + "integrity": "sha512-l4nitYCN/Ls57vtUfdextCjTjW41JD7lQiAnuR0RTbdByFc/6OmEAzwGd+lrp6CUtiXGQL1FCaYiamfHASrwBw==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/credential-provider-env": "^3.972.66", + "@aws-sdk/credential-provider-http": "^3.972.68", + "@aws-sdk/credential-provider-ini": "^3.973.11", + "@aws-sdk/credential-provider-process": "^3.972.66", + "@aws-sdk/credential-provider-sso": "^3.973.10", + "@aws-sdk/credential-provider-web-identity": "^3.972.72", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -248,15 +248,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "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==", + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.66.tgz", + "integrity": "sha512-YOnX6bIhdjx0QfaENu2PB0eFm5MEc9ft8XNGQ+NxMfeLSq9aE+XjWCwDupEnV4UWv5ZFpBLJbTREIx7KNOoqpQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -264,17 +264,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "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==", + "version": "3.973.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.10.tgz", + "integrity": "sha512-IsXnQ35j5VE+3ZK6aIhT5ypB+Jim3zRwVz0nYuVwyBKZyu/SYx+O2/LQpng8c2EiuwyqceabsDlYrICHDlJPsA==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/nested-clients": "^3.997.40", + "@aws-sdk/token-providers": "3.1102.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -282,16 +282,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "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==", + "version": "3.1102.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1102.0.tgz", + "integrity": "sha512-Ua700vVvM1q105yABSUQWkCK6FeTrNfU6ORGetJe5BzkZWY7QhkF7SVTOlmDGWRDNd6jbyY0Dv5e+E4bMBEmLg==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/nested-clients": "^3.997.40", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -299,16 +299,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "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==", + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.72.tgz", + "integrity": "sha512-nj9Zlsy7ya+fy+jhWTJwgfr7YdtDM4xHyZvgKuftuny0UgROVx9lxwvsWJSLvpKk4lig0m0tHng3k1fEnt0LeA==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/nested-clients": "^3.997.40", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -316,14 +316,14 @@ } }, "node_modules/@aws-sdk/eventstream-handler-node": { - "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==", + "version": "3.972.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.31.tgz", + "integrity": "sha512-/BRzvkp46mF6eXBL/l9WKPQQfifLlUPaWli6n9/T/WDLUg8he7TCyuNFnk6RvHP5j5W/kMj5Gxw7W778LJaXDA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -331,14 +331,14 @@ } }, "node_modules/@aws-sdk/middleware-eventstream": { - "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==", + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.26.tgz", + "integrity": "sha512-2eIvouTZoxPu5ClHY6ij13De1yhY8Rmllt0dlGeBNXX3wmR7fU1pvMCGb50fKm1GxcuntWK0t1T0cMjTyDoUQA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -346,17 +346,17 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "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==", + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.48.tgz", + "integrity": "sha512-1BYTN+c0J/n5HfoDl3IR2Cjhfp2coByofX+gOh8HhyXda1HP7oGAUI/uyKSn4Xc+rrvVcsSROTVF1ZRBs07ARQ==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -364,18 +364,18 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "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==", + "version": "3.997.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.40.tgz", + "integrity": "sha512-hEdHT0PBR4fkGxWhwKG5EtEYKnAM7HKkp0vD10ufk4YcXejH4r4q6G/XhPzjUc6Yxo5kBS2vHg7llj4ViR9VTQ==", "license": "Apache-2.0", "dependencies": { - "@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", + "@aws-sdk/core": "^3.977.5", + "@aws-sdk/signature-v4-multi-region": "^3.996.43", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -383,12 +383,12 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "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==", + "version": "4.9.13", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.13.tgz", + "integrity": "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.3", + "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, @@ -397,14 +397,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "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==", + "version": "3.996.43", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.43.tgz", + "integrity": "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -429,12 +429,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", - "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.16.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -454,12 +454,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", - "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz", + "integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.16.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -553,14 +553,15 @@ } }, "node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.10.tgz", - "integrity": "sha512-nwnOR3SuLYGRFfyQm8ri4Nj5VGVAvAM9GuqQd3u7BUQj0d6hmD2F8w7OHAAjThE3CuySIdM+v8E22QJG6/RfCg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.83.0.tgz", + "integrity": "sha512-RorGp9OH5l3ElpuC5a5ZQ2eWcchZGXflXRzVGkV99y3y6tT+LLNyxoYIdVKvTKWEObwhExeQbTH0fI2tE4iX4g==", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.80.10", + "@earendil-works/pi-ai": "^0.83.0", + "diff": "8.0.4", "ignore": "7.0.5", - "typebox": "1.1.38", + "typebox": "1.3.7", "yaml": "2.9.0" }, "engines": { @@ -568,9 +569,9 @@ } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.10.tgz", - "integrity": "sha512-Moe/H8c87yacDGK9dPbWphZNjVsrb3nTrIHycOQJAkFEnY9PYxOOd74+ny44kATfPU9Dm7aTHefar3pZF+UKUA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.83.0.tgz", + "integrity": "sha512-m3IZD4g3er0V8TC9+Vpgw/sjTKqcJlkcIBy/JvsgRubuuik3tAVzyugUg4rVrShIkkOT69mEd34NEqKUIsl6JQ==", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", @@ -583,7 +584,7 @@ "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", - "typebox": "1.1.38" + "typebox": "1.3.7" }, "bin": { "pi-ai": "dist/cli.js" @@ -593,15 +594,15 @@ } }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.10.tgz", - "integrity": "sha512-aL4apbupCHiVLSXASXvRzH4Q2vmtfrDa+0s909CJuVu/GgGylbDzr7oyF1mPmip5E+VxYYxKWmph4hV04wUcQg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.83.0.tgz", + "integrity": "sha512-uYhF+FsZxogoSX/AxBcUdiY+ZklubwaXyAoEGA2eQwsHcyEAhUYIKh/WLXe/a8+k8eTCmxb+ZN2Zo9mzQtzbWw==", "hasShrinkwrap": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.10", - "@earendil-works/pi-ai": "^0.80.10", - "@earendil-works/pi-tui": "^0.80.10", + "@earendil-works/pi-agent-core": "^0.83.0", + "@earendil-works/pi-ai": "^0.83.0", + "@earendil-works/pi-tui": "^0.83.0", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", @@ -614,7 +615,7 @@ "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "semver": "7.8.0", - "typebox": "1.1.38", + "typebox": "1.3.7", "undici": "8.5.0", "yaml": "2.9.0" }, @@ -1064,13 +1065,14 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.10.tgz", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.83.0.tgz", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.80.10", + "@earendil-works/pi-ai": "^0.83.0", + "diff": "8.0.4", "ignore": "7.0.5", - "typebox": "1.1.38", + "typebox": "1.3.7", "yaml": "2.9.0" }, "engines": { @@ -1078,8 +1080,8 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.10.tgz", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.83.0.tgz", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", @@ -1092,18 +1094,18 @@ "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", - "typebox": "1.1.38" + "typebox": "1.3.7" }, "bin": { - "pi-ai": "./dist/cli.js" + "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.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.83.0.tgz", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -1211,9 +1213,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1230,9 +1229,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1249,9 +1245,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1268,9 +1261,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1287,9 +1277,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1627,9 +1614,9 @@ "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", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -2197,9 +2184,9 @@ } }, "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==", + "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": { @@ -2312,9 +2299,9 @@ "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==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", + "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { @@ -2426,9 +2413,9 @@ } }, "node_modules/@earendil-works/pi-tui": { - "version": "0.80.10", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz", - "integrity": "sha512-c2JO29PbhKPEQ6fgHQKAl0WhwuFqzWfzspMmP+8B5tpDuP+0mvarRbKKg8gq4b+pQx/QX+6aVS4ko7deoyjQjg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.83.0.tgz", + "integrity": "sha512-IoYrb0rORjELmEpNtoCA/U8je3KopMkRAVJRdSzvXRvgb+Huo1gNh8Q5CSZvNOiYtDxJdj2tYZZHZ4B3+IN3hA==", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -3048,9 +3035,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3067,9 +3051,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3086,9 +3067,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3105,9 +3083,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3124,9 +3099,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3143,9 +3115,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3162,9 +3131,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3181,9 +3147,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3200,9 +3163,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3225,9 +3185,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3250,9 +3207,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3275,9 +3229,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3300,9 +3251,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3325,9 +3273,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3350,9 +3295,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3375,9 +3317,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -4307,9 +4246,9 @@ ] }, "node_modules/@smithy/core": { - "version": "3.29.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz", - "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==", + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.31.1.tgz", + "integrity": "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.16.1", @@ -4320,12 +4259,12 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz", - "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.16.tgz", + "integrity": "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.3", + "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, @@ -4334,12 +4273,12 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "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==", + "version": "5.6.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.13.tgz", + "integrity": "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.3", + "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, @@ -4374,12 +4313,12 @@ } }, "node_modules/@smithy/signature-v4": { - "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==", + "version": "5.6.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.12.tgz", + "integrity": "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.3", + "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, @@ -4787,6 +4726,15 @@ "node": ">=8" } }, + "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/dts-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", @@ -4971,9 +4919,9 @@ } }, "node_modules/gaxios": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", - "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -5011,9 +4959,9 @@ } }, "node_modules/google-auth-library": { - "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==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -5919,9 +5867,9 @@ } }, "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==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", + "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", "license": "MIT" }, "node_modules/typescript": { @@ -6205,12 +6153,12 @@ }, "packages/agent": { "name": "@onkernel/cua-agent", - "version": "0.9.0", + "version": "0.10.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "0.80.10", - "@earendil-works/pi-ai": "0.80.10", - "@onkernel/cua-ai": "0.9.0", + "@earendil-works/pi-agent-core": "0.83.0", + "@earendil-works/pi-ai": "0.83.0", + "@onkernel/cua-ai": "0.10.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.35.3" }, @@ -6224,10 +6172,10 @@ }, "packages/ai": { "name": "@onkernel/cua-ai", - "version": "0.9.0", + "version": "0.10.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "0.80.10", + "@earendil-works/pi-ai": "0.83.0", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, @@ -6241,13 +6189,14 @@ }, "packages/cli": { "name": "@onkernel/cua-cli", - "version": "0.8.0", + "version": "0.9.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.10", - "@earendil-works/pi-tui": "0.80.10", - "@onkernel/cua-agent": "0.9.0", - "@onkernel/cua-ai": "0.9.0", + "@earendil-works/pi-agent-core": "0.83.0", + "@earendil-works/pi-coding-agent": "0.83.0", + "@earendil-works/pi-tui": "0.83.0", + "@onkernel/cua-agent": "0.10.0", + "@onkernel/cua-ai": "0.10.0", "@onkernel/sdk": "0.49.0" }, "bin": { diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 647db16d..78f9f332 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 0.10.0 - 2026-08-04 + +Breaking: upgrade `@earendil-works/pi-agent-core` and `@earendil-works/pi-ai` +to 0.83.0 and adopt pi's context-first harness API. + +- `CuaAgentHarness` and `CuaAgentHarnessOptions` now take the tool context as + their first type parameter — `CuaAgentHarness` — mirroring pi's `AgentHarness` generic order. The + supplied `toolContext` is forwarded to pi untouched, and every executable + harness tool receives the exact object on each call. +- Executable harness tools are pi `AgentHarnessTool`s via the new + `CuaHarnessTool` union (a CUA spec or an `AgentHarnessTool`). + `CuaAgent` stays on the ordinary pi `AgentTool` (`CuaAgentTool`); the two + tool APIs are no longer conflated. +- Remove `CuaAgentHarnessOptions.env` and `CuaAgentHarness.env`. Execution + environments now travel through the tool context (for example + `toolContext: { env: new NodeExecutionEnv({ cwd }) }` for pi's + read/bash/edit/write tools). No alias is preserved. +- Remove `CuaSystemPromptCallback`; `systemPrompt` is pi's + `AgentHarnessSystemPrompt` through the harness options. +- Keep `streamFn` optional on `CuaAgentOptions` (CUA supplies its default + stream) even though pi 0.83.0 makes `AgentOptions.streamFn` required. +- Published declarations target pi's TypeBox 1.3 as-is; a downstream compile + test with `skipLibCheck: false` guards the packaged types. +- Preserve explicit Tzafon screenshot results in model context even when they + fall outside `toolResultImageReplayLimit`, because its native continuation + protocol requires those images. Other tool-result images remain bounded. + ## 0.9.0 - 2026-08-03 - Add OpenRouter Kimi K3 support through `@onkernel/cua-ai` 0.9.0, diff --git a/packages/agent/README.md b/packages/agent/README.md index 733074d7..93b9205d 100644 --- a/packages/agent/README.md +++ b/packages/agent/README.md @@ -48,7 +48,6 @@ compaction, steering, and follow-ups: import { CuaAgentHarness, InMemorySessionRepo, - NodeExecutionEnv, } from "@onkernel/cua-agent"; import { cua } from "@onkernel/cua-ai"; @@ -57,7 +56,6 @@ const session = await repo.create(); const harness = new CuaAgentHarness({ client, browser, - env: new NodeExecutionEnv({ cwd: process.cwd() }), session, model: "openai:gpt-5.6-sol", tools: cua.toolsets.browser(), @@ -70,6 +68,37 @@ await harness.prompt("Find the pricing page."); The package re-exports pi-agent-core session, skill, prompt-template, compaction, and execution-environment primitives used with the harness. +### Tool context + +Executable harness tools are pi `AgentHarnessTool`s: `execute` receives the +harness's tool context as its last argument. Supply it once as `toolContext` +and pi delivers the exact object (or the result of a zero-argument provider) +to every tool call: + +```ts +import { + CuaAgentHarness, + NodeExecutionEnv, + createBashTool, + createReadTool, + type ExecutionToolContext, +} from "@onkernel/cua-agent"; + +const harness = new CuaAgentHarness({ + client, + browser, + session, + model: "openai:gpt-5.6-sol", + tools: [createReadTool(), createBashTool(), ...cua.toolsets.browser()], + toolContext: { env: new NodeExecutionEnv({ cwd: process.cwd() }) }, + systemPrompt: "Use the supplied tools.", +}); +``` + +CUA specs and plain pi `AgentTool`s are accepted too — they simply ignore the +context. The low-level `CuaAgent` stays context-free: its tools are ordinary +pi `AgentTool`s (`CuaAgentTool`). + ## Choosing tools ```ts @@ -173,7 +202,9 @@ Tools return only requested feedback: textual markers. `toolResultImageReplayLimit` controls how many recent tool-result images remain -in model context (`4` by default, or `false` to disable projection). +in model context (`4` by default, or `false` to disable projection). Tzafon +native screenshot results are exempt because its continuation protocol requires +the image. ## Custom tools diff --git a/packages/agent/examples/harness-openai-smoke.ts b/packages/agent/examples/harness-openai-smoke.ts index 6654a6cf..e381413b 100644 --- a/packages/agent/examples/harness-openai-smoke.ts +++ b/packages/agent/examples/harness-openai-smoke.ts @@ -1,6 +1,6 @@ import Kernel from "@onkernel/sdk"; import { cua, requireCuaEnvApiKeyForModel, type CuaModelRef } from "@onkernel/cua-ai"; -import { CuaAgentHarness, InMemorySessionRepo, NodeExecutionEnv } from "../src/index"; +import { CuaAgentHarness, InMemorySessionRepo } from "../src/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; @@ -19,7 +19,6 @@ async function main(): Promise { const harness = new CuaAgentHarness({ browser, client, - env: new NodeExecutionEnv({ cwd: process.cwd() }), model: modelRef, session, // Prefer structured browser refs and semantic reads for the OpenAI smoke, diff --git a/packages/agent/examples/harness-provider-matrix.ts b/packages/agent/examples/harness-provider-matrix.ts index 5d7a7c06..1218945c 100644 --- a/packages/agent/examples/harness-provider-matrix.ts +++ b/packages/agent/examples/harness-provider-matrix.ts @@ -3,7 +3,7 @@ import { requireCuaEnvApiKeyForModel, type CuaModelRef, } from "@onkernel/cua-ai"; -import { CuaAgentHarness, InMemorySessionRepo, NodeExecutionEnv } from "../src/index"; +import { CuaAgentHarness, InMemorySessionRepo } from "../src/index"; import { logAgentEvent, logAssistant } from "./shared/logging"; import { SCENARIOS } from "./shared/scenarios"; import { toolsForModel } from "./shared/tools"; @@ -25,7 +25,6 @@ async function main(): Promise { const harness = new CuaAgentHarness({ browser, client, - env: new NodeExecutionEnv({ cwd: process.cwd() }), model: modelRef, session, tools: toolsForModel(modelRef), diff --git a/packages/agent/package.json b/packages/agent/package.json index a2e7cd25..6fcccca9 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-agent", - "version": "0.9.0", + "version": "0.10.0", "description": "Kernel browser computer-use Agent and AgentHarness classes built on pi-agent-core", "license": "MIT", "type": "module", @@ -43,9 +43,9 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-agent-core": "0.80.10", - "@earendil-works/pi-ai": "0.80.10", - "@onkernel/cua-ai": "0.9.0", + "@earendil-works/pi-agent-core": "0.83.0", + "@earendil-works/pi-ai": "0.83.0", + "@onkernel/cua-ai": "0.10.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.35.3" }, diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index d766d178..9e68b96c 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -6,12 +6,13 @@ import { type AgentHarnessEventResultMap, type AgentHarnessOptions, type AgentHarnessOwnEvent, + type AgentHarnessToolContextSource, type AgentHarnessResources, type AgentHarnessStreamOptions, + type AgentHarnessTool, type AgentMessage, type AgentOptions, type AgentTool, - type ExecutionEnv, type NavigateTreeResult, type PromptTemplate, type QueueMode, @@ -44,7 +45,7 @@ import { withProviderRetryModels, } from "./provider-retry"; import { CuaExecutionResources, type CuaExecutionDetails } from "./resources"; -import { CuaToolManager, type CuaAgentTool } from "./tool-manager"; +import { CuaToolManager, type CuaAgentTool, type CuaHarnessTool } from "./tool-manager"; import type { KernelBrowser } from "./translator/translator"; /** A registered CUA model reference or an already resolved pi model. */ @@ -53,7 +54,7 @@ export type CuaModelInput = CuaModelRef | Model; const DEFAULT_TOOL_RESULT_IMAGE_REPLAY_LIMIT = 4; const OMITTED_TOOL_RESULT_IMAGES = "[stale tool-result images omitted]"; -/** Maximum recent tool-result images retained in model context, or `false` to retain all images. */ +/** Maximum recent tool-result images retained in model context, or `false` to retain all images. Provider-required native tool images are always retained. */ export type ToolResultImageReplayLimit = number | false; /** Optional follow-up policy for otherwise empty successful assistant responses. */ @@ -82,34 +83,32 @@ type CuaAgentInitialState = Omit, "mod }; /** Construction options for {@link CuaAgent}, including its exact caller-owned tool catalog. */ -export type CuaAgentOptions = Omit & { +export type CuaAgentOptions = Omit & { browser: KernelBrowser; client: Kernel; tools: readonly CuaAgentTool[]; initialState: CuaAgentInitialState; + /** Defaults to streaming through the shared {@link cuaModels} collection. */ + streamFn?: AgentOptions["streamFn"]; emptyResponseRecovery?: CuaEmptyResponseRecoveryOptions; toolResultImageReplayLimit?: ToolResultImageReplayLimit; responseThreading?: boolean; retry?: CuaRetryOptions; }; -/** Harness callback used when callers compute their own system prompt. */ -export type CuaSystemPromptCallback< - TSkill extends Skill = Skill, - TPromptTemplate extends PromptTemplate = PromptTemplate, -> = NonNullable["systemPrompt"]>; - -/** Construction options for {@link CuaAgentHarness}, including its exact caller-owned tool catalog. */ -export type CuaAgentHarnessOptions< - TSkill extends Skill = Skill, - TPromptTemplate extends PromptTemplate = PromptTemplate, -> = Omit, "activeToolNames" | "model" | "models" | "tools"> & { +type CuaAgentHarnessOptionsBase< + TContext extends object | undefined, + TSkill extends Skill, + TPromptTemplate extends PromptTemplate, +> = Omit< + AgentHarnessOptions>, + "activeToolNames" | "model" | "models" | "tools" | "toolContext" | "retry" +> & { browser: KernelBrowser; client: Kernel; model: CuaModelInput; models?: Models; - tools: readonly CuaAgentTool[]; - systemPrompt?: string | CuaSystemPromptCallback; + tools: readonly CuaHarnessTool[]; onPayload?: SimpleStreamOptions["onPayload"]; emptyResponseRecovery?: CuaEmptyResponseRecoveryOptions; toolResultImageReplayLimit?: ToolResultImageReplayLimit; @@ -117,6 +116,25 @@ export type CuaAgentHarnessOptions< retry?: CuaRetryOptions; }; +/** + * Construction options for {@link CuaAgentHarness}, including its exact + * caller-owned tool catalog. Mirrors pi's `AgentHarnessOptions` generic order: + * the tool context first, then skill and prompt-template resource types. The + * supplied `toolContext` is forwarded to pi untouched, and every executable + * tool receives it on each call. + */ +export type CuaAgentHarnessOptions< + TContext extends object | undefined = undefined, + TSkill extends Skill = Skill, + TPromptTemplate extends PromptTemplate = PromptTemplate, +> = CuaAgentHarnessOptionsBase & ([TContext] extends [undefined] ? { + /** Context-free harnesses do not need a tool context. */ + toolContext?: undefined; +} : { + /** Static context or zero-argument context provider resolved for each turn snapshot. */ + toolContext: AgentHarnessToolContextSource; +}); + /** Pi Agent behavior with an explicit, identity-keyed CUA tool catalog. */ export class CuaAgent { private readonly coreAgent: Agent; @@ -185,6 +203,7 @@ export class CuaAgent { transformContext: async (messages, signal) => projectToolResultImages( transformContext ? await transformContext(messages, signal) : messages, imageReplayLimit, + manager.catalog.incoming.tzafonComputerName, ), prepareNextTurnWithContext: async (context, signal) => { const update = prepareNextTurnWithContext @@ -299,25 +318,24 @@ export class CuaAgent { /** Pi AgentHarness behavior through composition, without inherited active-tool APIs. */ export class CuaAgentHarness< + TContext extends object | undefined = undefined, TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate, > { - readonly env: ExecutionEnv; readonly models: Models; - private readonly coreHarness: AgentHarness; - private readonly tools: CuaToolManager; + private readonly coreHarness: AgentHarness>; + private readonly tools: CuaToolManager>; private emptyResponseRecoveryAttempts = 0; private hasPendingQueue = false; private toolTurnFailed = false; - constructor(options: CuaAgentHarnessOptions) { + constructor(options: CuaAgentHarnessOptions) { const { browser, client, model, models, tools: requestedTools, - systemPrompt, onPayload, emptyResponseRecovery, toolResultImageReplayLimit, @@ -330,20 +348,20 @@ export class CuaAgentHarness< const useResponseThreading = resolveResponseThreading(responseThreading); const resources = new CuaExecutionResources({ browser, client }); const retrying = withProviderRetryModels(models ?? cuaModels(), resolveProviderRetryPolicy(retry)); - const manager = new CuaToolManager(resources, model, requestedTools, (ref) => resolveModelFromCollection(ref, retrying)); + const manager = new CuaToolManager>(resources, model, requestedTools, (ref) => resolveModelFromCollection(ref, retrying)); const catalogModels = withCatalogModels(retrying, manager, imageReplayLimit, useResponseThreading); - const materialized = manager.agentTools(); - const core = new AgentHarness({ + const materialized = manager.harnessTools(); + // A generic TContext leaves pi's conditional toolContext unverifiable + // here; the spread forwards the caller's toolContext verbatim. + const core = new AgentHarness>({ ...harnessOptions, model: manager.catalog.model, models: catalogModels, tools: materialized, activeToolNames: materialized.map((tool) => tool.name), - systemPrompt, - }); + } as AgentHarnessOptions>); this.coreHarness = core; this.tools = manager; - this.env = core.env; this.models = core.models; core.on("tool_result", (event) => hasExecutionError(event.details) ? { isError: true } : undefined); @@ -379,12 +397,12 @@ export class CuaAgentHarness< } } - getTools(): readonly CuaAgentTool[] { return this.tools.getTools(); } + getTools(): readonly CuaHarnessTool[] { return this.tools.getTools(); } - async setTools(tools: readonly CuaAgentTool[]): Promise { - const previousTools = this.tools.agentTools(); + async setTools(tools: readonly CuaHarnessTool[]): Promise { + const previousTools = this.tools.harnessTools(); const prepared = this.tools.prepareTools(tools); - const materialized = this.tools.agentTools(prepared); + const materialized = this.tools.harnessTools(prepared); try { await this.coreHarness.setTools(materialized, materialized.map((tool) => tool.name)); } catch (error) { @@ -398,9 +416,9 @@ export class CuaAgentHarness< async setModel(model: CuaModelInput): Promise { const previousModel = this.tools.catalog.model; - const previousTools = this.tools.agentTools(); + const previousTools = this.tools.harnessTools(); const prepared = this.tools.prepareModel(model); - const materialized = this.tools.agentTools(prepared); + const materialized = this.tools.harnessTools(prepared); try { await this.coreHarness.setModel(prepared.catalog.model); await this.coreHarness.setTools(materialized, materialized.map((tool) => tool.name)); @@ -458,11 +476,15 @@ function resolveModelFromCollection(ref: CuaModelRef, models: Models): Model, imageReplayLimit: ToolResultImageReplayLimit, responseThreading: boolean, ): Models { - const contextFor = (context: Context) => projectModelContext(context, imageReplayLimit); + const contextFor = (context: Context) => projectModelContext( + context, + imageReplayLimit, + manager.catalog.incoming.tzafonComputerName, + ); const optionsFor = (options: T): T => { const catalog = manager.catalog; const callerOnPayload = options?.onPayload; @@ -503,15 +525,23 @@ function resolveToolResultImageReplayLimit(limit: ToolResultImageReplayLimit | u return limit; } -function projectToolResultImages(messages: TMessage[], limit: ToolResultImageReplayLimit): TMessage[] { +function projectToolResultImages( + messages: TMessage[], + limit: ToolResultImageReplayLimit, + requiredImageToolName?: string, +): TMessage[] { if (limit === false) return messages; let imageCount = 0; - for (const message of messages) if (message.role === "toolResult") imageCount += message.content.filter((block) => block.type === "image").length; + for (const message of messages) { + if (message.role === "toolResult" && message.toolName !== requiredImageToolName) { + imageCount += message.content.filter((block) => block.type === "image").length; + } + } if (imageCount <= limit) return messages; const firstRetainedImage = Math.max(0, imageCount - limit); let imageOrdinal = 0; return messages.map((message) => { - if (message.role !== "toolResult") return message; + if (message.role !== "toolResult" || message.toolName === requiredImageToolName) return message; let changed = false; let markerInserted = false; const content = [] as typeof message.content; @@ -530,8 +560,12 @@ function projectToolResultImages(messages: TMessa }); } -function projectModelContext(context: Context, imageReplayLimit: ToolResultImageReplayLimit): Context { - const messages = projectToolResultImages(context.messages, imageReplayLimit); +function projectModelContext( + context: Context, + imageReplayLimit: ToolResultImageReplayLimit, + requiredImageToolName?: string, +): Context { + const messages = projectToolResultImages(context.messages, imageReplayLimit, requiredImageToolName); return messages === context.messages ? context : { ...context, messages }; } @@ -555,7 +589,7 @@ function hasExecutionError(details: unknown): boolean { return Boolean(details && typeof details === "object" && (details as CuaExecutionDetails).isError === true); } -function turnFailureStopMessage(manager: CuaToolManager): string | undefined { +function turnFailureStopMessage(manager: CuaToolManager): string | undefined { for (const entry of manager.catalog.entries) { const execution = manager.specFor(entry.identity)?.execution; if (execution?.kind === "actions" && execution.stopTurnOnFailureMessage) return execution.stopTurnOnFailureMessage; diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 39ec4805..c96d9fd6 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -2,7 +2,7 @@ export * from "@earendil-works/pi-agent-core"; export { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; export { cua } from "@onkernel/cua-ai"; export type { CuaToolSpec } from "@onkernel/cua-ai"; -export type { CuaAgentTool } from "./tool-manager"; +export type { CuaAgentTool, CuaHarnessTool } from "./tool-manager"; export type { KernelBrowser } from "./translator/translator"; export { InternalComputerTranslator } from "./translator/translator"; @@ -37,7 +37,6 @@ export type { CuaAgentOptions, CuaAgentState, CuaEmptyResponseRecoveryOptions, - CuaSystemPromptCallback, ToolResultImageReplayLimit, } from "./agent"; export type { CuaRetryOptions } from "./provider-retry"; diff --git a/packages/agent/src/tool-manager.ts b/packages/agent/src/tool-manager.ts index 4e42043a..d19f0635 100644 --- a/packages/agent/src/tool-manager.ts +++ b/packages/agent/src/tool-manager.ts @@ -1,5 +1,5 @@ import { AsyncLocalStorage } from "node:async_hooks"; -import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core"; +import type { AgentHarnessTool, AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core"; import type { Api, Model } from "@earendil-works/pi-ai"; import { callerToolIdentity, @@ -15,21 +15,34 @@ import { import { CuaExecutionResources } from "./resources"; /** - * Caller-owned tool: a declarative CUA spec materialized by this package, or an - * already executable pi `AgentTool`. Defined here because cua-agent is the only - * package that holds both halves; cua-ai compiles declaration-only catalogs. + * Caller-owned tool for {@link CuaAgent}: a declarative CUA spec materialized + * by this package, or an already executable pi `AgentTool`. Defined here + * because cua-agent is the only package that holds both halves; cua-ai + * compiles declaration-only catalogs. */ export type CuaAgentTool = CuaToolSpec | AgentTool; +/** + * Caller-owned tool for {@link CuaAgentHarness}: a declarative CUA spec + * materialized by this package, or an executable pi `AgentHarnessTool` that + * receives the harness's tool context on every call. A plain `AgentTool` is + * assignable (it simply ignores the context), but the two APIs are kept + * distinct: `CuaAgent` takes `CuaAgentTool`, `CuaAgentHarness` takes this. + */ +export type CuaHarnessTool = CuaToolSpec | AgentHarnessTool; + /** * One atomically committable tools state. `requested` is the sole caller-owned - * source of truth; `catalog` is its pure declarative projection; `tools` are - * the wrapped executables joined back by identity after compilation. + * source of truth; `catalog` is its pure declarative projection; `tools` and + * `harnessTools` are the wrapped executables joined back by identity after + * compilation, viewed as pi `AgentTool`s (for `CuaAgent`) or as + * context-delivering `AgentHarnessTool`s (for `CuaAgentHarness`). */ -export interface PreparedCuaTools { - readonly requested: readonly CuaAgentTool[]; +export interface PreparedCuaTools = CuaAgentTool> { + readonly requested: readonly TRequested[]; readonly catalog: CuaToolCatalog; readonly tools: readonly AgentTool[]; + readonly harnessTools: readonly AgentHarnessTool[]; /** Identity → CUA spec, for execution metadata (e.g. stop-on-failure policy). */ readonly specs: ReadonlyMap; /** Declaration fingerprint composed with implementation identity, in entry order. */ @@ -39,7 +52,7 @@ export interface PreparedCuaTools { interface ToolExecutionScope { readonly toolName: string; readonly executionMode: AgentTool["executionMode"]; - readonly baseline: PreparedCuaTools; + readonly baseline: PreparedCuaTools; } /** @@ -61,14 +74,14 @@ function implementationId(key: object): number { } /** Owns the caller's requested list and its compiled catalog while browser resources live independently. */ -export class CuaToolManager { +export class CuaToolManager = CuaAgentTool> { private readonly execution = new AsyncLocalStorage(); - private current: PreparedCuaTools; + private current: PreparedCuaTools; constructor( readonly resources: CuaExecutionResources, model: CuaModelRef | Model, - requestedTools: readonly CuaAgentTool[], + requestedTools: readonly TRequested[], private readonly resolveModel: (model: CuaModelRef) => Model = getCuaModel, ) { this.current = this.prepare(model, requestedTools); @@ -78,31 +91,36 @@ export class CuaToolManager { return this.current.catalog; } - getTools(): readonly CuaAgentTool[] { + getTools(): TRequested[] { return [...this.current.requested]; } - /** Wrapped executable tools for a prepared (or the committed) state, in entry order. */ - agentTools(prepared: PreparedCuaTools = this.current): AgentTool[] { + /** Wrapped pi `AgentTool` view of a prepared (or the committed) state, in entry order. */ + agentTools(prepared: PreparedCuaTools = this.current): AgentTool[] { return [...prepared.tools]; } + /** Wrapped pi `AgentHarnessTool` view of a prepared (or the committed) state, in entry order. */ + harnessTools(prepared: PreparedCuaTools = this.current): AgentHarnessTool[] { + return [...prepared.harnessTools]; + } + /** Execution metadata for one committed catalog identity. */ specFor(identity: string): CuaToolSpec | undefined { return this.current.specs.get(identity); } - prepareTools(tools: readonly CuaAgentTool[]): PreparedCuaTools { + prepareTools(tools: readonly TRequested[]): PreparedCuaTools { this.assertMutationScope("setTools"); return this.prepare(this.current.catalog.model, tools); } - prepareModel(model: CuaModelRef | Model): PreparedCuaTools { + prepareModel(model: CuaModelRef | Model): PreparedCuaTools { this.assertMutationScope("setModel"); return this.prepare(model, this.current.requested); } - commit(prepared: PreparedCuaTools): void { + commit(prepared: PreparedCuaTools): void { this.current = prepared; } @@ -112,10 +130,10 @@ export class CuaToolManager { * compilation, then joined back strictly by compiled identity — never by * position. Any failure leaves the committed state untouched. */ - private prepare(model: CuaModelRef | Model, tools: readonly CuaAgentTool[]): PreparedCuaTools { + private prepare(model: CuaModelRef | Model, tools: readonly TRequested[]): PreparedCuaTools { const requested = Object.freeze([...tools]); const inputs: CuaCatalogToolInput[] = []; - const executables = new Map(); + const executables = new Map(); const implementations = new Map(); const specs = new Map(); for (const tool of requested) { @@ -140,7 +158,7 @@ export class CuaToolManager { }); const fingerprints: string[] = []; - const wrapped = catalog.entries.map((entry) => { + const joined: Array> = catalog.entries.map((entry) => { const executable = executables.get(entry.identity); const implementation = implementations.get(entry.identity); if (!executable || !implementation) { @@ -148,8 +166,7 @@ export class CuaToolManager { } executables.delete(entry.identity); fingerprints.push(`${entry.fingerprint}#impl-${implementationId(implementation)}`); - const agentTool = isCuaToolSpec(executable) ? this.resources.materialize(executable) : executable; - return this.wrapExecutable(agentTool); + return isCuaToolSpec(executable) ? this.resources.materialize(executable) : (executable as AgentHarnessTool); }); if (executables.size > 0) { throw new Error(`requested tool(s) ${[...executables.keys()].join(", ")} missing from the compiled catalog`); @@ -158,29 +175,46 @@ export class CuaToolManager { return Object.freeze({ requested, catalog, - tools: Object.freeze(wrapped), + tools: Object.freeze(joined.map((tool) => this.wrapAgentExecutable(tool as AgentTool))), + harnessTools: Object.freeze(joined.map((tool) => this.wrapHarnessExecutable(tool))), specs, fingerprints: Object.freeze(fingerprints), }); } - private wrapExecutable(tool: AgentTool): AgentTool { + /** Low-level `AgentTool` view for {@link CuaAgent}; caller tools there never declare a harness context. */ + private wrapAgentExecutable(tool: AgentTool): AgentTool { + return { + ...tool, + execute: (toolCallId, input, signal, onUpdate) => + this.executeWithScope(tool, () => tool.execute(toolCallId, input, signal, onUpdate)), + }; + } + + /** Context-delivering `AgentHarnessTool` view for {@link CuaAgentHarness}. */ + private wrapHarnessExecutable(tool: AgentHarnessTool): AgentHarnessTool { return { ...tool, - execute: async (toolCallId, input, signal, onUpdate) => { - const scope: ToolExecutionScope = { - toolName: tool.name, - executionMode: tool.executionMode, - baseline: this.current, - }; - return this.execution.run(scope, async () => { - const result = await tool.execute(toolCallId, input, signal, onUpdate); - return mergeAddedToolNames(result, cachePreservingAdditions(scope.baseline, this.current) ?? []); - }); - }, + execute: (toolCallId, params, signal, onUpdate, context) => + this.executeWithScope(tool, () => tool.execute(toolCallId, params, signal, onUpdate, context)), }; } + private async executeWithScope( + tool: { readonly name: string; readonly executionMode?: AgentTool["executionMode"] }, + call: () => Promise>, + ): Promise> { + const scope: ToolExecutionScope = { + toolName: tool.name, + executionMode: tool.executionMode, + baseline: this.current, + }; + return this.execution.run(scope, async () => { + const result = await call(); + return mergeAddedToolNames(result, cachePreservingAdditions(scope.baseline, this.current) ?? []); + }); + } + private assertMutationScope(api: "setTools" | "setModel"): void { const scope = this.execution.getStore(); if (scope && scope.executionMode !== "sequential") { @@ -189,7 +223,7 @@ export class CuaToolManager { } } -function cachePreservingAdditions(previous: PreparedCuaTools, next: PreparedCuaTools): string[] | undefined { +function cachePreservingAdditions(previous: PreparedCuaTools, next: PreparedCuaTools): string[] | undefined { const previousModel = previous.catalog.model; const nextModel = next.catalog.model; if (previousModel.provider !== nextModel.provider || previousModel.id !== nextModel.id || previousModel.api !== nextModel.api) return undefined; diff --git a/packages/agent/test/agent.test.ts b/packages/agent/test/agent.test.ts index f437923b..395e23ad 100644 --- a/packages/agent/test/agent.test.ts +++ b/packages/agent/test/agent.test.ts @@ -4,6 +4,7 @@ import { createCuaModels, getCuaModel, cua, + isCuaToolSpec, type AssistantMessage, type Context, type Model, @@ -15,7 +16,7 @@ import { CuaAgent, CuaAgentHarness, InMemorySessionRepo, - NodeExecutionEnv, + type AgentMessage, type AgentTool, type KernelBrowser, type StreamFn, @@ -54,7 +55,7 @@ function scriptedStream( const stream = createAssistantMessageEventStream(); const message = turns[call++]?.(model) ?? assistant(model); stream.push({ type: "start", partial: message }); - stream.push({ type: "done", reason: message.stopReason, message }); + stream.push({ type: "done", reason: message.stopReason as "stop" | "length" | "toolUse", message }); stream.end(message); return stream; }; @@ -74,7 +75,6 @@ function callerTool(name: string, execute?: AgentTool["execute"], executionMode? async function harnessServices() { const repo = new InMemorySessionRepo(); return { - env: new NodeExecutionEnv({ cwd: process.cwd() }), session: await repo.create(), }; } @@ -104,6 +104,34 @@ describe("CuaAgent explicit tools", () => { expect("getMode" in agent).toBe(false); }); + it("retains protocol-required Tzafon screenshot results outside the image replay limit", async () => { + const contexts: Context[] = []; + const model = getCuaModel("tzafon:tzafon.northstar-cua-fast"); + const nativeComputer = cua.providers.tzafon.tools.computer(); + const ordinaryTool = callerTool("ordinary"); + const image = { type: "image" as const, data: "c2NyZWVuc2hvdA==", mimeType: "image/png" }; + const messages: AgentMessage[] = [ + assistant(model, [{ type: "toolCall", id: "native-shot", name: "computer", arguments: { action: { type: "screenshot" } } }], "toolUse"), + { role: "toolResult", toolCallId: "native-shot", toolName: "computer", content: [image], isError: false, timestamp: 1 }, + assistant(model, [{ type: "toolCall", id: "ordinary-shot", name: "ordinary", arguments: {} }], "toolUse"), + { role: "toolResult", toolCallId: "ordinary-shot", toolName: "ordinary", content: [image], isError: false, timestamp: 2 }, + ]; + const agent = new CuaAgent({ + browser, + client, + tools: [nativeComputer, ordinaryTool], + streamFn: scriptedStream([(selectedModel) => assistant(selectedModel)], contexts), + toolResultImageReplayLimit: 0, + initialState: { model, messages }, + }); + + await agent.prompt("continue"); + + const results = contexts[0]!.messages.filter((message) => message.role === "toolResult"); + expect(results[0]!.content).toEqual([image]); + expect(results[1]!.content).toEqual([{ type: "text", text: "[stale tool-result images omitted]" }]); + }); + it("installs exact native-only, native-plus-CUA, Playwright-only, and browser-act-only catalogs", () => { const custom = callerTool("customer_lookup"); const cases = [ @@ -435,7 +463,8 @@ describe("CuaAgentHarness explicit tools", () => { await harness.setTools([cua.providers.anthropic.tools.browser()]); await expect(harness.setModel("openai:gpt-5.5")).rejects.toThrow(/requires a anthropic model/); expect(harness.getModel().provider).toBe("anthropic"); - expect(harness.getTools()[0]?.identity).toBe("provider.anthropic.native.browser.20260701"); + const installed = harness.getTools()[0]; + expect(installed && isCuaToolSpec(installed) ? installed.identity : undefined).toBe("provider.anthropic.native.browser.20260701"); }); it("keeps the harness catalog and executors unchanged when setTools fails", async () => { diff --git a/packages/agent/test/e2e.live.test.ts b/packages/agent/test/e2e.live.test.ts index 05fbbdae..a9d347e0 100644 --- a/packages/agent/test/e2e.live.test.ts +++ b/packages/agent/test/e2e.live.test.ts @@ -5,7 +5,6 @@ import { CuaAgentHarness, cua, InMemorySessionRepo, - NodeExecutionEnv, type AgentEvent, type AgentHarnessEvent, type AgentMessage, @@ -240,7 +239,6 @@ async function withBrowser(run: (client: Kernel, browser: Awaited, content: AssistantMessage["content"] = [], stopReason: AssistantMessage["stopReason"] = "stop"): AssistantMessage { + return { + role: "assistant", + content, + api: model.api, + provider: model.provider, + model: model.id, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason, + timestamp: Date.now(), + }; +} + +function scriptedStream(turns: Array<(model: Model) => AssistantMessage>): StreamFn { + let call = 0; + return (model) => { + const stream = createAssistantMessageEventStream(); + const message = turns[call++]?.(model) ?? assistant(model); + stream.push({ type: "start", partial: message }); + stream.push({ type: "done", reason: message.stopReason as "stop" | "length" | "toolUse", message }); + stream.end(message); + return stream; + }; +} + +function modelsFromStream(streamFn: StreamFn, provider = "openai") { + const models = createCuaModels(); + models.setProvider({ + id: provider, + name: "scripted", + auth: { apiKey: { name: "test", resolve: async () => ({ auth: { apiKey: "test" } }) } }, + getModels: () => [], + stream: streamFn, + streamSimple: streamFn, + } as never); + return models; +} + +async function harnessSession() { + return new InMemorySessionRepo().create(); +} + +describe("CuaAgentHarness tool context", () => { + it("delivers the exact supplied context object to custom harness tools", async () => { + interface CustomContext { + env: ExecutionToolContext["env"]; + requestId: string; + } + const cwd = mkdtempSync(join(tmpdir(), "cua-harness-context-")); + const toolContext: CustomContext = { env: new NodeExecutionEnv({ cwd }), requestId: "req-1" }; + const received: CustomContext[] = []; + const custom: AgentHarnessTool = { + name: "custom_context", + label: "custom_context", + description: "records its tool context", + parameters: { type: "object", properties: {}, additionalProperties: false } as never, + execute: async (_toolCallId, _params, _signal, _onUpdate, context) => { + received.push(context); + return { content: [{ type: "text", text: "ok" }], details: {} }; + }, + }; + const harness = new CuaAgentHarness({ + browser, + client, + session: await harnessSession(), + model: "openai:gpt-5.5", + models: modelsFromStream(scriptedStream([ + (model) => assistant(model, [{ type: "toolCall", id: "call-1", name: "custom_context", arguments: {} }], "toolUse"), + (model) => assistant(model, [{ type: "text", text: "done" }]), + ])), + tools: [custom], + toolContext, + }); + + await harness.prompt("run the tool"); + + expect(received.length).toBe(1); + expect(received[0]).toBe(toolContext); + }); + + it("runs pi's native read/write/edit/bash tools against the context's execution env", async () => { + const cwd = mkdtempSync(join(tmpdir(), "cua-harness-tools-")); + const harness = new CuaAgentHarness({ + browser, + client, + session: await harnessSession(), + model: "openai:gpt-5.5", + models: modelsFromStream(scriptedStream([ + (model) => assistant(model, [{ type: "toolCall", id: "write-1", name: "write", arguments: { path: "notes.txt", content: "hello cua\n" } }], "toolUse"), + (model) => assistant(model, [{ type: "toolCall", id: "edit-1", name: "edit", arguments: { path: "notes.txt", edits: [{ oldText: "hello", newText: "goodbye" }] } }], "toolUse"), + (model) => assistant(model, [{ type: "toolCall", id: "read-1", name: "read", arguments: { path: "notes.txt" } }], "toolUse"), + (model) => assistant(model, [{ type: "toolCall", id: "bash-1", name: "bash", arguments: { command: "cat notes.txt" } }], "toolUse"), + (model) => assistant(model, [{ type: "text", text: "done" }]), + ])), + tools: [createReadTool(), createBashTool(), createEditTool(), createWriteTool()], + toolContext: { env: new NodeExecutionEnv({ cwd }) }, + }); + const results = new Map; isError: boolean }>(); + harness.on("tool_result", (event) => { + results.set(event.toolName, { content: event.content, isError: event.isError }); + return undefined; + }); + + await harness.prompt("write, edit, read, and cat a file"); + + expect(readFileSync(join(cwd, "notes.txt"), "utf8")).toBe("goodbye cua\n"); + expect(results.get("write")?.isError).toBe(false); + expect(results.get("edit")?.isError).toBe(false); + expect(results.get("read")?.content.some((block) => block.text?.includes("goodbye cua"))).toBe(true); + expect(results.get("bash")?.content.some((block) => block.text?.includes("goodbye cua"))).toBe(true); + }); +}); diff --git a/packages/agent/test/published-declarations.test.ts b/packages/agent/test/published-declarations.test.ts new file mode 100644 index 00000000..2442aa41 --- /dev/null +++ b/packages/agent/test/published-declarations.test.ts @@ -0,0 +1,165 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, it } from "vitest"; + +const agentRoot = fileURLToPath(new URL("..", import.meta.url)); +const repoRoot = resolve(agentRoot, "..", ".."); + +/** + * Downstream consumer compiled against the packaged declarations (dist/) with + * skipLibCheck disabled, so transitive declaration issues in cua-agent, + * cua-ai, pi, or TypeBox surface here instead of after publish. + */ +const CONSUMER = ` +import { Type } from "typebox"; +import { + CuaAgent, + CuaAgentHarness, + InMemorySessionRepo, + NodeExecutionEnv, + createBashTool, + createEditTool, + createReadTool, + createWriteTool, + type AgentHarnessTool, + type CuaAgentTool, + type CuaHarnessTool, + type ExecutionToolContext, + type KernelBrowser, +} from "@onkernel/cua-agent"; +import { cua, getCuaModel } from "@onkernel/cua-ai"; +import type Kernel from "@onkernel/sdk"; + +declare const browser: KernelBrowser; +declare const client: Kernel; + +const toolContext: ExecutionToolContext = { env: new NodeExecutionEnv({ cwd: process.cwd() }) }; + +const custom: AgentHarnessTool = { + name: "custom", + label: "custom", + description: "custom tool", + parameters: Type.Object({}), + execute: async (_toolCallId, _params, _signal, _onUpdate, context) => { + const delivered: ExecutionToolContext = context; + void delivered; + return { content: [{ type: "text", text: "ok" }], details: undefined }; + }, +}; + +const harnessTools: readonly CuaHarnessTool[] = [ + ...cua.toolsets.browser(), + createReadTool(), + createBashTool(), + createEditTool(), + createWriteTool(), + custom, +]; + +const agentTools: readonly CuaAgentTool[] = [...cua.toolsets.browser()]; + +async function build() { + const session = await new InMemorySessionRepo().create(); + const harness = new CuaAgentHarness({ + browser, + client, + session, + model: "openai:gpt-5.6-sol", + tools: harnessTools, + toolContext, + }); + const contextFree = new CuaAgentHarness({ + browser, + client, + session, + model: getCuaModel("openai:gpt-5.6-sol"), + tools: [], + }); + new CuaAgentHarness({ + browser, + client, + session, + model: "openai:gpt-5.6-sol", + tools: [], + // @ts-expect-error env was removed; the tool context now carries the execution env + env: new NodeExecutionEnv({ cwd: process.cwd() }), + }); + const agent = new CuaAgent({ browser, client, tools: agentTools, initialState: { model: "openai:gpt-5.6-sol" } }); + return { harness, contextFree, agent }; +} + +void build; +`; + +function run(command: string, args: string[], cwd: string): void { + try { + execFileSync(command, args, { cwd, stdio: "pipe" }); + } catch (error) { + const failure = error as { stdout?: Buffer; stderr?: Buffer }; + throw new Error( + `${command} ${args.join(" ")} failed:\n${failure.stdout?.toString() ?? ""}${failure.stderr?.toString() ?? ""}`, + { cause: error }, + ); + } +} + +// skipLibCheck: false checks the whole reachable declaration graph. Two +// vendored SDK typings cannot compile under it in a hoisted node_modules +// layout, independent of cua: @anthropic-ai/sdk unions import() fallbacks +// for nested layouts that cannot resolve, and @google/genai references an +// optional MCP package and DOM event globals. Tolerate exactly those two +// files' errors; anything else — above all our dist declarations, pi's, or +// TypeBox's — fails the test. +const TOLERATED = [ + /node_modules\/\@anthropic-ai\/sdk\/.*TS2307/, + /node_modules\/\@google\/genai\/.*TS(2307|2304|2552)/, +]; + +function runTsc(tsconfigDir: string): void { + try { + execFileSync(process.execPath, [join(repoRoot, "node_modules", "typescript", "bin", "tsc"), "-p", tsconfigDir], { stdio: "pipe" }); + return; + } catch (error) { + const failure = error as { stdout?: Buffer; stderr?: Buffer }; + const output = `${failure.stdout?.toString() ?? ""}${failure.stderr?.toString() ?? ""}`; + const errors = output.trim().split("\n").filter((line) => line.length > 0); + const unexpected = errors.filter((line) => !TOLERATED.some((pattern) => pattern.test(line))); + if (unexpected.length === 0) return; + throw new Error(`tsc reported unexpected declaration errors:\n${unexpected.join("\n")}`, { cause: error }); + } +} + +describe("published declarations", () => { + it("compile a downstream consumer with skipLibCheck: false", { timeout: 300_000 }, () => { + run("npm", ["run", "build", "--workspace", "@onkernel/cua-ai"], repoRoot); + run("npm", ["run", "build", "--workspace", "@onkernel/cua-agent"], repoRoot); + + const dir = mkdtempSync(join(tmpdir(), "cua-declarations-")); + writeFileSync(join(dir, "consumer.ts"), CONSUMER); + writeFileSync(join(dir, "tsconfig.json"), JSON.stringify({ + compilerOptions: { + target: "ES2022", + module: "ESNext", + moduleResolution: "Bundler", + lib: ["ES2022"], + types: ["node"], + typeRoots: [join(repoRoot, "node_modules", "@types")], + strict: true, + skipLibCheck: false, + noEmit: true, + baseUrl: repoRoot, + paths: { + "@onkernel/cua-ai": ["packages/ai/dist/index.d.ts"], + "@onkernel/cua-agent": ["packages/agent/dist/index.d.ts"], + "*": ["./node_modules/*"], + }, + }, + include: ["consumer.ts"], + }, null, 2)); + + runTsc(dir); + }); +}); diff --git a/packages/agent/test/tool-manager.test.ts b/packages/agent/test/tool-manager.test.ts index 57085f0c..db0b0f75 100644 --- a/packages/agent/test/tool-manager.test.ts +++ b/packages/agent/test/tool-manager.test.ts @@ -4,6 +4,7 @@ import type Kernel from "@onkernel/sdk"; import { CuaExecutionResources, type AgentTool, + type CuaAgentTool, type KernelBrowser, } from "../src/index"; import { CuaToolManager } from "../src/tool-manager"; @@ -96,7 +97,7 @@ describe("CuaToolManager implementation identity", () => { const resources = setup(); const spy = vi.spyOn(resources, "materialize"); const spec = cua.tools.browser.snapshot(); - const manager = new CuaToolManager(resources, "openai:gpt-5.5", [spec]); + const manager = new CuaToolManager(resources, "openai:gpt-5.5", [spec]); manager.commit(manager.prepareModel("openai:gpt-5.6-sol")); manager.commit(manager.prepareTools([...manager.getTools(), callerTool("added")])); diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 66e58bd6..2d336ad6 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 0.10.0 - 2026-08-04 + +Breaking: upgrade `@earendil-works/pi-ai` to 0.83.0. + +- Remove the local `claude-opus-5`, `gemini-3.6-flash`, and + `gemini-3.5-flash-lite` model overrides: pi-ai 0.83.0's registry now carries + all three with the same metadata. Overrides remain only for the CUA-only + providers pi-ai does not ship (Meta, Tzafon, Yutori). +- Kimi K3 reasoning effort follows pi-ai's catalog metadata with no CUA + override: `low`/`high`/`max` map through `thinkingLevelMap` (the rest clamp + away), and requests carry `reasoning_effort` on Moonshot or OpenRouter's + nested `reasoning.effort`. +- Disable Tzafon native non-screenshot action loops: its Responses API requires + every `computer_call_output` to carry an image, while CUA returns screenshots + only when explicitly requested. Unsupported native actions now fail before + browser execution instead of entering a text-only loop that the API rejects. + ## 0.9.0 - 2026-08-03 - Add Kimi K3 through OpenRouter as `openrouter:moonshotai/kimi-k3`, diff --git a/packages/ai/README.md b/packages/ai/README.md index f2869653..23542c50 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -1,7 +1,7 @@ # `@onkernel/cua-ai` The model and tool-policy layer for Kernel computer-use agents, built on -`@earendil-works/pi-ai` 0.80.10. +`@earendil-works/pi-ai` 0.83.0. Use [`@onkernel/cua-agent`](../agent) when you also want Kernel-browser tool execution. Use this package directly for model discovery, explicit tool catalog @@ -233,7 +233,7 @@ unioned and deduplicated; exact-value conflicts throw. ## Dynamic loading metadata -Ordinary function tools are marked eligible only where pi 0.80.10 supports +Ordinary function tools are marked eligible only where pi 0.83.0 supports deferred loading. Provider-native tools are eager-only. The catalog itself does not guess when tools were added; `CuaAgent`/`CuaAgentHarness` record in-tool additions through pi's active-tool change entries. @@ -249,7 +249,9 @@ additions through pi's active-tool change entries. - **Meta/xAI/Moonshot**: ordinary function tools with serial tool calls when the selected catalog mutates browser state. - **Tzafon**: identity-scoped native declaration replacement with actual viewport - dimensions. + dimensions. Explicit screenshot and terminal answer actions are supported; + non-screenshot native action loops fail before browser execution because + Tzafon's continuation protocol requires implicit post-action screenshots. - **Yutori**: identity-scoped native `tool_set`/`disable_tools` fields while preserving ordinary function tools such as an explicitly selected screenshot. diff --git a/packages/ai/package.json b/packages/ai/package.json index 1053a833..31d39c19 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-ai", - "version": "0.9.0", + "version": "0.10.0", "description": "Kernel-curated computer-use model access built on pi-ai", "license": "MIT", "type": "module", @@ -44,7 +44,7 @@ "test:integration": "vitest --run --config vitest.integration.config.ts" }, "dependencies": { - "@earendil-works/pi-ai": "0.80.10", + "@earendil-works/pi-ai": "0.83.0", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index 2b8f72a5..07049201 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -128,16 +128,9 @@ export const CUA_MODEL_ANNOTATIONS: Record[]> = { openai: [], - // Opus 5 is live in Anthropic's API and models.dev but is newer than the - // pinned pi-ai catalog. Remove this override after the next pi-ai bump. - anthropic: [anthropicOpus5Model()], - // Google's current computer-use models are live but newer than the pinned - // pi-ai catalog. Remove each override after pi-ai adds it. - google: [ - cuaModel("google", "gemini-3.6-flash", "Gemini 3.6 Flash"), - cuaModel("google", "gemini-3.5-flash-lite", "Gemini 3.5 Flash-Lite"), - ], - // pi-ai 0.80.10 still lacks Meta's models.dev catalog entry. + anthropic: [], + google: [], + // pi-ai still lacks Meta's models.dev catalog entry. meta: [cuaModel("meta", "muse-spark-1.1", "Muse Spark 1.1")], xai: [], moonshotai: [], @@ -319,40 +312,17 @@ function isCuaFamilyMatch(id: string, family: string): boolean { .every((segment) => /^\d+$/.test(segment)); } -function anthropicOpus5Model(): Model { - return { - id: "claude-opus-5", - name: "Claude Opus 5", - api: "anthropic-messages", - provider: "anthropic", - baseUrl: "https://api.anthropic.com", - compat: { forceAdaptiveThinking: true, supportsTemperature: false }, - reasoning: true, - thinkingLevelMap: { xhigh: "xhigh", max: "max" }, - input: ["text", "image"], - cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }, - contextWindow: 1_000_000, - maxTokens: 128_000, - }; -} - -function cuaModel(provider: Exclude, id: string, name: string): Model { +function cuaModel(provider: "meta" | "tzafon" | "yutori", id: string, name: string): Model { const base = { id, name, provider, - reasoning: provider === "openai" || provider === "anthropic" || provider === "google" || provider === "meta", + reasoning: provider === "meta", input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, } satisfies Partial>; switch (provider) { - case "openai": - return { ...base, api: OPENAI_CUA_RESPONSES_API, baseUrl: "https://api.openai.com/v1", contextWindow: 400_000, maxTokens: 32_768 } as Model; - case "anthropic": - return { ...base, api: "anthropic-messages", baseUrl: "https://api.anthropic.com", contextWindow: 200_000, maxTokens: 64_000 } as Model; - case "google": - return { ...base, api: "google-generative-ai", baseUrl: "https://generativelanguage.googleapis.com/v1beta", contextWindow: 1_048_576, maxTokens: 65_536 } as Model; case "meta": // Meta documents the 1,048,576-token context window, and its // computer-use cookbook configures 128,000 maximum output tokens. diff --git a/packages/ai/src/providers/openai/provider.ts b/packages/ai/src/providers/openai/provider.ts index 378382ab..6e4047e2 100644 --- a/packages/ai/src/providers/openai/provider.ts +++ b/packages/ai/src/providers/openai/provider.ts @@ -58,8 +58,8 @@ export const streamSimpleOpenAIResponses: StreamFunction action.type !== "answer"); const nativeName = options?.cuaIncomingToolPlan?.tzafonComputerName; + const textOnlyAction = executable.find((action) => action.type !== "screenshot"); + if (nativeName && textOnlyAction) { + throw new Error( + `Tzafon native computer action "${textOnlyAction.type}" is unsupported: its action loop requires an automatic post-action screenshot, and CUA returns screenshots only when explicitly requested.`, + ); + } if (nativeName && executable.length > 0) { emitToolCall(stream, output, { type: "toolCall", id: callId, name: nativeName, arguments: { action: rawAction } }); continue; @@ -338,12 +344,15 @@ function convertMessages(messages: readonly Message[], nativeComputerName?: stri .trim(); const image = [...message.content].reverse().find((part): part is ImageContent => part.type === "image"); if (nativeComputerName && message.toolName === nativeComputerName) { + if (!image) { + throw new Error( + "Tzafon native computer action loops require image tool results; text-only results are unsupported because CUA does not capture post-action screenshots automatically.", + ); + } items.push({ type: "computer_call_output", call_id: message.toolCallId, - output: image - ? { type: "computer_screenshot", image_url: `data:${image.mimeType};base64,${image.data}` } - : { type: "computer_screenshot", error: message.isError ? text || "tool execution failed" : text || "ok" }, + output: { type: "computer_screenshot", image_url: `data:${image.mimeType};base64,${image.data}` }, }); continue; } diff --git a/packages/ai/test/models.test.ts b/packages/ai/test/models.test.ts index 9744c398..ddea4ba5 100644 --- a/packages/ai/test/models.test.ts +++ b/packages/ai/test/models.test.ts @@ -59,7 +59,7 @@ describe("CUA model refs", () => { expect(model.api).toBe("yutori-chat-completions"); const opus = getCuaModel("anthropic:claude-opus-5"); - expect(cuaOverrideModels("anthropic").map((entry) => entry.id)).toContain("claude-opus-5"); + expect(cuaOverrideModels("anthropic")).toEqual([]); expect(opus).toMatchObject({ provider: "anthropic", api: "anthropic-messages", @@ -70,8 +70,7 @@ describe("CUA model refs", () => { }); expect(opus.compat).toMatchObject({ forceAdaptiveThinking: true, supportsTemperature: false }); - const googleOverrides = cuaOverrideModels("google").map((entry) => entry.id); - expect(googleOverrides).toEqual(["gemini-3.6-flash", "gemini-3.5-flash-lite"]); + expect(cuaOverrideModels("google")).toEqual([]); expect(getCuaModel("google:gemini-3.6-flash")).toMatchObject({ provider: "google", api: GOOGLE_CUA_INTERACTIONS_API, @@ -118,9 +117,11 @@ describe("CUA model refs", () => { expect(kimi.input).toContain("image"); expect(kimi.contextWindow).toBe(1_048_576); expect(kimi.maxTokens).toBe(131_072); - // K3 launched with max-only thinking effort; pi-ai clamps the rest away. + // Pi's K3 metadata maps low/high/max and clamps the rest away. + expect(kimi.thinkingLevelMap?.low).toBe("low"); + expect(kimi.thinkingLevelMap?.high).toBe("high"); expect(kimi.thinkingLevelMap?.max).toBe("max"); - expect(kimi.thinkingLevelMap?.high).toBeNull(); + expect(kimi.thinkingLevelMap?.medium).toBeNull(); }); it("accepts the moonshot: alias for moonshotai refs", () => { diff --git a/packages/ai/test/tzafon-provider.test.ts b/packages/ai/test/tzafon-provider.test.ts index 6cc58036..8208527a 100644 --- a/packages/ai/test/tzafon-provider.test.ts +++ b/packages/ai/test/tzafon-provider.test.ts @@ -67,22 +67,65 @@ describe("streamTzafonResponses", () => { expect(calls[0]!.arguments).toEqual({ actions: [{ type: "click", x: 10, y: 20 }] }); }); - it("normalizes computer_call actions with string coordinates", async () => { + it("normalizes non-native computer_call actions with string coordinates", async () => { responsesCreate.mockResolvedValueOnce({ id: "resp_2", usage: {}, output: [{ type: "computer_call", call_id: "call_2", action: { type: "left_click", x: "500", y: "250" } }], }); + const message = await tzafon.streamTzafonResponses(model, { messages: [] }, { apiKey: "test" }).result(); + expect(message.stopReason).toBe("toolUse"); + const calls = toolCalls(message.content); + expect(calls).toHaveLength(1); + expect(calls[0]!.name).toBe("click"); + expect(calls[0]!.arguments).toEqual({ x: 500, y: 250 }); + }); + + it("rejects native computer actions that require automatic post-action screenshots", async () => { + responsesCreate.mockResolvedValueOnce({ + id: "resp_native_click", + usage: {}, + output: [{ type: "computer_call", call_id: "call_click", action: { type: "click", x: 500, y: 500 } }], + }); + const message = await tzafon.streamTzafonResponses(model, { messages: [] }, { apiKey: "test", cuaIncomingToolPlan: { tzafonComputerName: "computer", yutoriNames: {}, googleNames: {}, googleExcludedNames: [], nativeToolNames: ["computer"] }, }).result(); - expect(message.stopReason).toBe("toolUse"); - const calls = toolCalls(message.content); - expect(calls).toHaveLength(1); - expect(calls[0]!.name).toBe("computer"); - expect(calls[0]!.arguments).toEqual({ action: { type: "left_click", x: "500", y: "250" } }); + + expect(message.stopReason).toBe("error"); + expect(message.errorMessage).toContain('Tzafon native computer action "click" is unsupported'); + expect(toolCalls(message.content)).toEqual([]); + }); + + it("rejects text-only native computer results before sending a request", () => { + expect(() => tzafon.buildTzafonRequestInput(model, { + messages: [ + { + role: "assistant", + content: [{ type: "toolCall", id: "call_click", name: "computer", arguments: { action: { type: "click", x: 500, y: 500 } } }], + api: model.api, + provider: model.provider, + model: model.id, + 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: 1, + }, + { + role: "toolResult", + toolCallId: "call_click", + toolName: "computer", + content: [{ type: "text", text: "Actions executed successfully." }], + isError: false, + timestamp: 2, + }, + ], + tools: [], + }, { + disableResponseThreading: true, + cuaIncomingToolPlan: { tzafonComputerName: "computer", yutoriNames: {}, googleNames: {}, googleExcludedNames: [], nativeToolNames: ["computer"] }, + })).toThrow("text-only results are unsupported"); }); it("degrades malformed function-call arguments to empty args instead of failing the turn", async () => { @@ -95,15 +138,12 @@ describe("streamTzafonResponses", () => { ], }); - const message = await tzafon.streamTzafonResponses(model, { messages: [] }, { - apiKey: "test", - cuaIncomingToolPlan: { tzafonComputerName: "computer", yutoriNames: {}, googleNames: {}, googleExcludedNames: [], nativeToolNames: ["computer"] }, - }).result(); + const message = await tzafon.streamTzafonResponses(model, { messages: [] }, { apiKey: "test" }).result(); expect(message.stopReason).toBe("toolUse"); expect(message.errorMessage).toBeUndefined(); const calls = toolCalls(message.content); expect(calls).toHaveLength(2); expect(calls[0]!).toMatchObject({ name: "custom_tool", arguments: {} }); - expect(calls[1]!).toMatchObject({ name: "computer", arguments: { action: { type: "left_click", x: 1, y: 2 } } }); + expect(calls[1]!).toMatchObject({ name: "click", arguments: { x: 1, y: 2 } }); }); }); diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index cb94ad4d..94b8418f 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.9.0 - 2026-08-04 + +Breaking: upgrade the pi stack to 0.83.0 (`pi-ai`, `pi-agent-core`, +`pi-coding-agent`, `pi-tui`). + +- Coding tools now come from `@earendil-works/pi-agent-core` + (`createReadTool`/`createBashTool`/`createEditTool`/`createWriteTool`, + preserving the read/bash/edit/write order) instead of pi-coding-agent's + `createCodingTools`. The harness supplies + `toolContext: { env: new NodeExecutionEnv({ cwd }) }`, and the removed + `CuaAgentHarness` `env` option is no longer used. +- Kimi K3 (Moonshot and OpenRouter) sends `reasoning_effort: low` at the + default `--thinking` level, following pi's catalog metadata with no CLI + override. +- Update `@onkernel/cua-ai` and `@onkernel/cua-agent` to 0.10.0. + ## 0.8.0 - 2026-08-03 - Queue messages submitted during an active turn for steering at the next agent diff --git a/packages/cli/README.md b/packages/cli/README.md index 8fe43f0f..134727b0 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -183,8 +183,11 @@ whose API rejects `browser_act`'s larger schema; Anthropic's native browser tool when supported; Google's native browser action set; Tzafon's native computer tool in a browser environment; and Yutori's native N1/N1.5 browser set. If the active Anthropic credential cannot access `browser_20260701`, the same selected -browser tool uses its equivalent function transport. Library callers can select -any catalog directly; see [`@onkernel/cua-agent`](../agent). +browser tool uses its equivalent function transport. Tzafon's native tool allows +explicit screenshots and terminal answers only; non-screenshot actions fail +before browser execution because CUA does not synthesize the post-action images +its continuation protocol requires. Library callers can select any catalog +directly; see [`@onkernel/cua-agent`](../agent). ## Output formats diff --git a/packages/cli/package.json b/packages/cli/package.json index c10c2dce..f97c5c3c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-cli", - "version": "0.8.0", + "version": "0.9.0", "description": "Kernel-cloud-browser computer-use TUI built on @onkernel/cua-agent and pi-tui", "license": "MIT", "type": "module", @@ -35,10 +35,11 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.10", - "@earendil-works/pi-tui": "0.80.10", - "@onkernel/cua-agent": "0.9.0", - "@onkernel/cua-ai": "0.9.0", + "@earendil-works/pi-agent-core": "0.83.0", + "@earendil-works/pi-coding-agent": "0.83.0", + "@earendil-works/pi-tui": "0.83.0", + "@onkernel/cua-agent": "0.10.0", + "@onkernel/cua-ai": "0.10.0", "@onkernel/sdk": "0.49.0" }, "devDependencies": { diff --git a/packages/cli/src/action/harness-runner.ts b/packages/cli/src/action/harness-runner.ts index a6886383..c4f5f001 100644 --- a/packages/cli/src/action/harness-runner.ts +++ b/packages/cli/src/action/harness-runner.ts @@ -1,11 +1,12 @@ -import type { AgentHarnessEvent, CuaAgentHarness } from "@onkernel/cua-agent"; +import type { AgentHarnessEvent } from "@onkernel/cua-agent"; +import type { CuaCliHarness } from "../harness"; import type { AssistantMessage } from "@onkernel/cua-ai"; import { stderr, stdout } from "node:process"; import { type ActionRequest, buildPrompt, DEFAULT_MAX_TURNS } from "./prompts"; import { type ActionEventInfo, type ActionResult, exitCodeFor, formatCompact, parseResult } from "./result"; export interface HarnessRunOptions { - harness: CuaAgentHarness; + harness: CuaCliHarness; maxTurns?: number; } diff --git a/packages/cli/src/cli-harness.ts b/packages/cli/src/cli-harness.ts index 053e94d3..b600e205 100644 --- a/packages/cli/src/cli-harness.ts +++ b/packages/cli/src/cli-harness.ts @@ -445,7 +445,7 @@ async function finishHarnessRuntime( const thinkingLevel = mapThinkingLevel(flags.thinking); const baseUrlOverride = providerBaseUrlOverride(provider); - const applicationTools = defaultApplicationTools(cwd); + const applicationTools = defaultApplicationTools(); const harness = buildCuaHarness({ cwd, client: provisioned.handle.client, @@ -487,7 +487,8 @@ function providerBaseUrlOverride(provider: string): string | undefined { return value && value.length > 0 ? value : undefined; } -function mapThinkingLevel(raw: string | undefined): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" { +/** Map a `--thinking` flag value to pi's thinking level; unset/empty defaults to `"low"`, and invalid values throw. */ +export function mapThinkingLevel(raw: string | undefined): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" { const v = (raw ?? "low").trim().toLowerCase(); switch (v) { case "off": diff --git a/packages/cli/src/harness.ts b/packages/cli/src/harness.ts index 5d8585b7..488c6593 100644 --- a/packages/cli/src/harness.ts +++ b/packages/cli/src/harness.ts @@ -1,10 +1,9 @@ import { CuaAgentHarness, type CuaAgentHarnessOptions, - type CuaAgentTool, + type CuaHarnessTool, formatSkillsForSystemPrompt, type KernelBrowser, - NodeExecutionEnv, type Session, type Skill, type ThinkingLevel, @@ -19,9 +18,21 @@ import { parseCuaModelRef, } from "@onkernel/cua-ai"; import type Kernel from "@onkernel/sdk"; -import { createCodingTools } from "@earendil-works/pi-coding-agent"; +import { + createBashTool, + createEditTool, + createReadTool, + createWriteTool, + type ExecutionToolContext, +} from "@earendil-works/pi-agent-core"; +import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node"; import type { ContextFile } from "./harness-skills"; +/** CLI harness: a CUA harness whose tool context carries the coding tools' execution environment. */ +export type CuaCliHarness = CuaAgentHarness; +/** One tool in the CLI harness's caller-owned list. */ +export type CuaCliTool = CuaHarnessTool; + export interface BuildCuaHarnessOptions { cwd: string; client: Kernel; @@ -32,7 +43,7 @@ export interface BuildCuaHarnessOptions { contextFiles?: ContextFile[]; thinkingLevel?: ThinkingLevel; /** Override the CLI's explicit interaction + coding tool list. */ - tools?: CuaAgentTool[]; + tools?: CuaCliTool[]; models?: Models; toolResultImageReplayLimit?: CuaAgentHarnessOptions["toolResultImageReplayLimit"]; responseThreading?: CuaAgentHarnessOptions["responseThreading"]; @@ -41,7 +52,7 @@ export interface BuildCuaHarnessOptions { } /** Build the CLI harness with one explicit tool list and a caller-owned prompt. */ -export function buildCuaHarness(opts: BuildCuaHarnessOptions): CuaAgentHarness { +export function buildCuaHarness(opts: BuildCuaHarnessOptions): CuaCliHarness { const skills = opts.skills ?? []; const contextFiles = opts.contextFiles ?? []; const model: CuaModelRef | Model = opts.modelBaseUrl @@ -49,19 +60,19 @@ export function buildCuaHarness(opts: BuildCuaHarnessOptions): CuaAgentHarness { : opts.model; const tools = opts.tools ?? [ ...defaultInteractionTools(opts.model), - ...defaultApplicationTools(opts.cwd), + ...defaultApplicationTools(), ]; - return new CuaAgentHarness({ - env: new NodeExecutionEnv({ cwd: opts.cwd }), + return new CuaAgentHarness({ session: opts.session, model, + models: opts.models, browser: opts.browser, client: opts.client, tools, + toolContext: { env: new NodeExecutionEnv({ cwd: opts.cwd }) }, resources: { skills }, thinkingLevel: opts.thinkingLevel, systemPrompt: ({ resources }) => composeSystemPrompt(resources.skills ?? [], contextFiles), - models: opts.models, toolResultImageReplayLimit: opts.toolResultImageReplayLimit, responseThreading: opts.responseThreading, retry: opts.retry, @@ -69,20 +80,20 @@ export function buildCuaHarness(opts: BuildCuaHarnessOptions): CuaAgentHarness { } /** Coding tools owned by the CLI application rather than inferred from a compiled catalog. */ -export function defaultApplicationTools(cwd: string): CuaAgentTool[] { - return createCodingTools(cwd); +export function defaultApplicationTools(): CuaCliTool[] { + return [createReadTool(), createBashTool(), createEditTool(), createWriteTool()]; } /** * CLI structured-browser policy. `browser_act` remains outside the reusable * base toolset, so the application opts into semantic verified plans explicitly. */ -function structuredBrowserTools(): CuaAgentTool[] { +function structuredBrowserTools(): CuaCliTool[] { return [...cua.toolsets.browser(), cua.tools.browser.act()]; } /** CLI policy is explicit application composition, not a CuaAgent default. */ -export function defaultInteractionTools(model: CuaModelRef): CuaAgentTool[] { +export function defaultInteractionTools(model: CuaModelRef): CuaCliTool[] { const { provider, model: modelId } = parseCuaModelRef(model); switch (provider) { case "openai": diff --git a/packages/cli/src/output/harness-jsonl.ts b/packages/cli/src/output/harness-jsonl.ts index 5b4b8673..c3bf79a2 100644 --- a/packages/cli/src/output/harness-jsonl.ts +++ b/packages/cli/src/output/harness-jsonl.ts @@ -1,8 +1,8 @@ import type { AgentHarnessEvent, - CuaAgentHarness, KernelBrowser, } from "@onkernel/cua-agent"; +import type { CuaCliHarness } from "../harness"; /** * Schema version stamped on every `session_created` event. Bump when the @@ -11,7 +11,7 @@ import type { export const CUA_JSONL_SCHEMA_VERSION = 1; export interface JsonlSinkOptions { - harness: CuaAgentHarness; + harness: CuaCliHarness; browser: KernelBrowser; modelRef: string; provider: string; diff --git a/packages/cli/src/print.ts b/packages/cli/src/print.ts index aae842b8..e25a973b 100644 --- a/packages/cli/src/print.ts +++ b/packages/cli/src/print.ts @@ -1,11 +1,12 @@ -import type { AgentHarnessEvent, CuaAgentHarness, Skill } from "@onkernel/cua-agent"; +import type { AgentHarnessEvent, Skill } from "@onkernel/cua-agent"; +import type { CuaCliHarness } from "./harness"; import { stderr, stdout } from "node:process"; import type { CuaBrowserHandle } from "./harness-browser"; import { attachHarnessJsonlSink } from "./output/harness-jsonl"; import { parseSkillInvocation } from "./harness-skills"; export interface RunPrintOptions { - harness: CuaAgentHarness; + harness: CuaCliHarness; browserHandle: CuaBrowserHandle; modelRef: string; provider: string; diff --git a/packages/cli/src/tui/main.ts b/packages/cli/src/tui/main.ts index db7b4ff5..9e1d52c8 100644 --- a/packages/cli/src/tui/main.ts +++ b/packages/cli/src/tui/main.ts @@ -1,8 +1,6 @@ import { type AgentHarnessEvent, type AgentMessage, - type CuaAgentHarness, - type CuaAgentTool, estimateContextTokens, type Session, type Skill, @@ -22,6 +20,7 @@ import { import { initTheme } from "@earendil-works/pi-coding-agent"; import { homedir } from "node:os"; import { type CuaModelRef, listCuaModels, type Model } from "@onkernel/cua-ai"; +import { type CuaCliHarness, type CuaCliTool } from "../harness"; import type { CuaBrowserHandle } from "../harness-browser"; import { resolveCuaModelRef } from "../harness-models"; import { updateNamedSessionRuntime } from "../harness-named-sessions"; @@ -43,7 +42,7 @@ import { cuaVersion } from "./version"; export interface InteractiveOptions { cwd: string; - harness: CuaAgentHarness; + harness: CuaCliHarness; browserHandle: CuaBrowserHandle; session: Session; skills?: Skill[]; @@ -53,9 +52,9 @@ export interface InteractiveOptions { modelRef: string; provider: string; /** Coding tools explicitly owned by the CLI and retained across /model switches. */ - applicationTools: readonly CuaAgentTool[]; + applicationTools: readonly CuaCliTool[]; /** Optional CLI application policy for replacing interaction tools on /model. */ - interactionToolsForModel?: (model: CuaModelRef) => readonly CuaAgentTool[]; + interactionToolsForModel?: (model: CuaModelRef) => readonly CuaCliTool[]; initialPrompt?: string; /** Image protocol override: kitty | iterm2 | none | auto (default: auto). */ imageProtocol?: string; @@ -72,7 +71,7 @@ export interface InteractiveOptions { /** * Run the interactive cua TUI: pi-tui differential renderer with header, * message list, sticky screenshot widget, editor (autocomplete-backed slash - * commands), status line, and telemetry footer. Drives a {@link CuaAgentHarness} + * commands), status line, and telemetry footer. Drives a {@link CuaCliHarness} * directly via `harness.subscribe()`. */ export async function runInteractive(opts: InteractiveOptions): Promise { @@ -195,7 +194,7 @@ export async function runInteractive(opts: InteractiveOptions): Promise // The full list the application composed for the active model. `/tools` // selections are subsets of this; it is never grown by the picker, so an // unsupported tool can never be added. - let baselineTools: readonly CuaAgentTool[] = composeBaselineTools(opts, currentModelRef); + let baselineTools: readonly CuaCliTool[] = composeBaselineTools(opts, currentModelRef); let toolSelectionCustomized = false; // Serializes every catalog mutation (`/tools` applies and `/model` switches); // see mutation-queue.ts for why they must not interleave. @@ -373,7 +372,7 @@ export async function runInteractive(opts: InteractiveOptions): Promise // The exact list installed by this switch, kept so it can become the new // `/tools` baseline. Undefined when the caller supplies no interaction // policy, in which case the switch never touches the tool list at all. - let installedTools: readonly CuaAgentTool[] | undefined; + let installedTools: readonly CuaCliTool[] | undefined; if (opts.interactionToolsForModel) { const previousModel = opts.harness.getModel(); const previousTools = opts.harness.getTools(); @@ -767,7 +766,7 @@ function tryResolveModelRef(input: string | undefined): CuaModelRef | undefined * so the baseline is never rebased on `harness.getTools()`, which a `/tools` * customization would have shrunk. */ -function composeBaselineTools(opts: InteractiveOptions, ref: CuaModelRef | undefined): readonly CuaAgentTool[] { +function composeBaselineTools(opts: InteractiveOptions, ref: CuaModelRef | undefined): readonly CuaCliTool[] { if (!opts.interactionToolsForModel || !ref) return opts.harness.getTools(); return [...opts.interactionToolsForModel(ref), ...opts.applicationTools]; } diff --git a/packages/cli/src/tui/tool-selection.ts b/packages/cli/src/tui/tool-selection.ts index e60cebe0..73a187db 100644 --- a/packages/cli/src/tui/tool-selection.ts +++ b/packages/cli/src/tui/tool-selection.ts @@ -1,5 +1,5 @@ import { callerToolIdentity, isCuaToolSpec } from "@onkernel/cua-ai"; -import type { CuaAgentTool } from "@onkernel/cua-agent"; +import type { CuaCliTool } from "../harness"; /** Where a tool came from, used purely as a display badge. */ export type ToolGroup = "native" | "cua" | "application"; @@ -25,16 +25,16 @@ export interface ToolSelectionItem { } /** Identity key for a caller-owned tool, using cua-ai's canonical identity helper. */ -export function toolKey(tool: CuaAgentTool): string { +export function toolKey(tool: CuaCliTool): string { return isCuaToolSpec(tool) ? tool.identity : callerToolIdentity(tool.name); } -function toolGroup(tool: CuaAgentTool): ToolGroup { +function toolGroup(tool: CuaCliTool): ToolGroup { if (!isCuaToolSpec(tool)) return "application"; return tool.origin === "provider-native" ? "native" : "cua"; } -function atomicGroupOf(tool: CuaAgentTool): string | undefined { +function atomicGroupOf(tool: CuaCliTool): string | undefined { if (!isCuaToolSpec(tool)) return undefined; const binding = tool.providerBinding; if (binding?.kind === "yutori-native" && binding.generation === "n1") { @@ -43,7 +43,7 @@ function atomicGroupOf(tool: CuaAgentTool): string | undefined { return undefined; } -function toolDescription(tool: CuaAgentTool): string | undefined { +function toolDescription(tool: CuaCliTool): string | undefined { const raw = isCuaToolSpec(tool) ? tool.declaration.description : tool.description; if (typeof raw !== "string") return undefined; const firstLine = raw.trim().split("\n")[0]?.trim(); @@ -56,7 +56,7 @@ function toolDescription(tool: CuaAgentTool): string | undefined { * so applying a selection can filter the baseline in place and keep the * provider-native catalog and application policy byte-for-byte identical. */ -export function describeTools(tools: readonly CuaAgentTool[]): ToolSelectionItem[] { +export function describeTools(tools: readonly CuaCliTool[]): ToolSelectionItem[] { return tools.map((tool) => { const description = toolDescription(tool); const atomicGroup = atomicGroupOf(tool); diff --git a/packages/cli/test/fixtures/harness.ts b/packages/cli/test/fixtures/harness.ts index f2ef342b..ca5a5e99 100644 --- a/packages/cli/test/fixtures/harness.ts +++ b/packages/cli/test/fixtures/harness.ts @@ -1,5 +1,4 @@ import { - type CuaAgentTool, InMemorySessionRepo, type Session, type Skill, @@ -8,7 +7,7 @@ import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; import { join } from "node:path"; import { parseCuaModelRef } from "@onkernel/cua-ai"; -import { buildCuaHarness, defaultInteractionTools } from "../../src/harness"; +import { buildCuaHarness, type CuaCliTool, defaultInteractionTools } from "../../src/harness"; import { createFakeKernelEnvironment, type FakeKernelEnvironment } from "./fake-kernel"; import type { ScriptedProviderHandle, ScriptedTurn } from "./scripted-provider"; import { createScriptedCuaModels } from "./scripted-provider"; @@ -26,7 +25,7 @@ export interface BuildTestHarnessOptions { skills?: Skill[]; /** CUA model ref. Defaults to the CLI's OpenAI default. */ modelRef?: string; - tools?: CuaAgentTool[]; + tools?: CuaCliTool[]; retry?: Parameters[0]["retry"]; } diff --git a/packages/cli/test/fixtures/scripted-provider.ts b/packages/cli/test/fixtures/scripted-provider.ts index 3514d5ed..d6763fa1 100644 --- a/packages/cli/test/fixtures/scripted-provider.ts +++ b/packages/cli/test/fixtures/scripted-provider.ts @@ -148,7 +148,7 @@ function buildStream(model: Model, turn: ScriptedTurn | undefined, signal?: stream.push({ type: "toolcall_end", contentIndex, - toolCall: { id, name: step.toolName, arguments: step.args }, + toolCall: { type: "toolCall", id, name: step.toolName, arguments: step.args }, partial: message, }); contentIndex += 1; diff --git a/packages/cli/test/harness-assembly.test.ts b/packages/cli/test/harness-assembly.test.ts index 1c13e1f7..d6e92507 100644 --- a/packages/cli/test/harness-assembly.test.ts +++ b/packages/cli/test/harness-assembly.test.ts @@ -4,7 +4,6 @@ import { InMemorySessionRepo, type Skill, } from "@onkernel/cua-agent"; -import { createCodingTools } from "@earendil-works/pi-coding-agent"; import { cua } from "@onkernel/cua-ai"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; @@ -59,10 +58,8 @@ describe("buildCuaHarness", () => { expect(toolNames).toContain("browser_click"); expect(toolNames).toContain("browser_screenshot"); expect(toolNames).toContain("browser_act"); - const codingToolNames = createCodingTools(cwd).map((tool) => tool.name); - for (const name of codingToolNames) { - expect(toolNames).toContain(name); - } + // pi's native read/bash/edit/write coding tools close the list, in order. + expect(toolNames.slice(-4)).toEqual(["read", "bash", "edit", "write"]); }); it("uses only the caller-owned skill block as its system prompt", async () => { diff --git a/packages/cli/test/kimi-reasoning-payload.test.ts b/packages/cli/test/kimi-reasoning-payload.test.ts new file mode 100644 index 00000000..43fd6238 --- /dev/null +++ b/packages/cli/test/kimi-reasoning-payload.test.ts @@ -0,0 +1,74 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { InMemorySessionRepo } from "@onkernel/cua-agent"; +import { createCuaModels, type CuaModelRef } from "@onkernel/cua-ai"; +import { mapThinkingLevel } from "../src/cli-harness"; +import { buildCuaHarness } from "../src/harness"; +import { createFakeKernelEnvironment } from "./fixtures/fake-kernel"; + +const SSE_CHUNKS = [ + 'data: {"id":"chatcmpl-k3","object":"chat.completion.chunk","created":1,"model":"kimi-k3","choices":[{"index":0,"delta":{"role":"assistant","content":"ok"},"finish_reason":null}]}\n\n', + 'data: {"id":"chatcmpl-k3","object":"chat.completion.chunk","created":1,"model":"kimi-k3","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}\n\n', + "data: [DONE]\n\n", +]; + +function sseResponse(): Response { + const body = new ReadableStream({ + start(controller) { + for (const chunk of SSE_CHUNKS) controller.enqueue(new TextEncoder().encode(chunk)); + controller.close(); + }, + }); + return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } }); +} + +/** Run one turn against the real pi provider with fetch stubbed, returning the request payloads. */ +async function capturePayloads(apiKeyEnv: string, model: CuaModelRef): Promise[]> { + vi.stubEnv(apiKeyEnv, "test-key"); + vi.stubGlobal("fetch", vi.fn(async () => sseResponse())); + const kernel = createFakeKernelEnvironment(); + const harness = buildCuaHarness({ + cwd: mkdtempSync(join(tmpdir(), "cua-kimi-payload-")), + client: kernel.client, + browser: kernel.browser, + session: await new InMemorySessionRepo().create(), + model, + thinkingLevel: mapThinkingLevel(undefined), + models: createCuaModels(), + tools: [], + }); + const payloads: Record[] = []; + harness.on("before_provider_payload", (event) => { + payloads.push(event.payload as Record); + return undefined; + }); + await harness.prompt("hi"); + return payloads; +} + +describe("Kimi K3 reasoning effort", () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.unstubAllEnvs(); + }); + + it("resolves the CLI default to low when --thinking is not passed", () => { + expect(mapThinkingLevel(undefined)).toBe("low"); + }); + + it("sends reasoning_effort: low to Moonshot at the CLI default thinking level", async () => { + const payloads = await capturePayloads("MOONSHOT_API_KEY", "moonshotai:kimi-k3"); + expect(payloads.length).toBe(1); + expect(payloads[0]?.model).toBe("kimi-k3"); + expect(payloads[0]?.reasoning_effort).toBe("low"); + }); + + it("sends reasoning effort through OpenRouter's nested reasoning object", async () => { + const payloads = await capturePayloads("OPENROUTER_API_KEY", "openrouter:moonshotai/kimi-k3"); + expect(payloads.length).toBe(1); + expect(payloads[0]?.model).toBe("moonshotai/kimi-k3"); + expect(payloads[0]?.reasoning).toEqual({ effort: "low" }); + }); +}); diff --git a/packages/cli/test/tool-revalidation.test.ts b/packages/cli/test/tool-revalidation.test.ts index b9fb3635..d2c5dac7 100644 --- a/packages/cli/test/tool-revalidation.test.ts +++ b/packages/cli/test/tool-revalidation.test.ts @@ -12,7 +12,7 @@ import { buildTestHarness } from "./fixtures/harness"; describe("/tools selection revalidation", () => { it("accepts a partial Google native subset", async () => { const modelRef = "google:gemini-3.6-flash"; - const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools(process.cwd())]; + const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools()]; const fixture = await buildTestHarness({ turns: [], modelRef, tools: baseline }); const items = describeTools(baseline); @@ -26,7 +26,7 @@ describe("/tools selection revalidation", () => { it("rejects a partial Yutori n1 native subset and leaves the catalog unchanged", async () => { const modelRef = "yutori:n1-latest"; - const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools(process.cwd())]; + const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools()]; const fixture = await buildTestHarness({ turns: [], modelRef, tools: baseline }); const before = fixture.harness.getTools().map(toolKey); @@ -41,7 +41,7 @@ describe("/tools selection revalidation", () => { it("accepts dropping the whole Yutori n1 native group", async () => { const modelRef = "yutori:n1-latest"; - const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools(process.cwd())]; + const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools()]; const fixture = await buildTestHarness({ turns: [], modelRef, tools: baseline }); const items = describeTools(baseline); @@ -54,7 +54,7 @@ describe("/tools selection revalidation", () => { it("accepts an empty selection (text-only agent)", async () => { const modelRef = "openai:gpt-5.6-sol"; - const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools(process.cwd())]; + const baseline = [...defaultInteractionTools(modelRef), ...defaultApplicationTools()]; const fixture = await buildTestHarness({ turns: [], modelRef, tools: baseline }); await fixture.harness.setTools([]); @@ -65,7 +65,7 @@ describe("/tools selection revalidation", () => { const cwd = process.cwd(); const from = "openai:gpt-5.6-sol"; const to = "anthropic:claude-opus-5"; - const application = defaultApplicationTools(cwd); + const application = defaultApplicationTools(); const fixture = await buildTestHarness({ turns: [], modelRef: from, diff --git a/packages/cli/test/tool-selection.test.ts b/packages/cli/test/tool-selection.test.ts index bfcb1273..7ada4e18 100644 --- a/packages/cli/test/tool-selection.test.ts +++ b/packages/cli/test/tool-selection.test.ts @@ -20,7 +20,7 @@ describe("toolKey", () => { }); it("namespaces a plain caller tool as caller.", () => { - const coding = defaultApplicationTools(cwd); + const coding = defaultApplicationTools(); expect(coding.length).toBeGreaterThan(0); for (const tool of coding) { expect(toolKey(tool)).toBe(`caller.${tool.name}`); @@ -30,14 +30,14 @@ describe("toolKey", () => { describe("describeTools", () => { it("preserves the baseline order the application composed", () => { - const baseline = [...defaultInteractionTools("openai:gpt-5.6-sol"), ...defaultApplicationTools(cwd)]; + const baseline = [...defaultInteractionTools("openai:gpt-5.6-sol"), ...defaultApplicationTools()]; expect(describeTools(baseline).map((item) => item.key)).toEqual(baseline.map(toolKey)); }); it("labels provider-native, cua, and application groups", () => { const items = describeTools([ ...defaultInteractionTools("google:gemini-3.6-flash"), - ...defaultApplicationTools(cwd), + ...defaultApplicationTools(), ]); const groups = new Set(items.map((item) => item.group)); expect(groups.has("native")).toBe(true); @@ -73,7 +73,7 @@ describe("toolSearchText", () => { }); describe("selection state machine", () => { - const baseline = [...defaultInteractionTools("openai:gpt-5.6-sol"), ...defaultApplicationTools(cwd)]; + const baseline = [...defaultInteractionTools("openai:gpt-5.6-sol"), ...defaultApplicationTools()]; const items = describeTools(baseline); const allKeys = items.map((item) => item.key); diff --git a/packages/cli/test/tui.fixture.test.ts b/packages/cli/test/tui.fixture.test.ts index d81e8f5b..063e976e 100644 --- a/packages/cli/test/tui.fixture.test.ts +++ b/packages/cli/test/tui.fixture.test.ts @@ -51,7 +51,7 @@ const STABLE_MS = 250; * added or removed, while still pinning the exact number the picker shows. */ function baselineToolCount(modelRef: string): number { - return defaultInteractionTools(modelRef as CuaModelRef).length + defaultApplicationTools(cwd).length; + return defaultInteractionTools(modelRef as CuaModelRef).length + defaultApplicationTools().length; } suite("TUI ptywright scenarios", () => {