Compare commits

..
52 changed files with 3268 additions and 4099 deletions
+180 -247
View File
@@ -110,9 +110,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.2.133.tgz",
"integrity": "sha512-J79AWcnoPaVU55fLAcdUdgYuj4PjXx1qkZGHtoKAMQ7XMktH21nW/VTFkIo/aKcGDsbSYNxMtw+zp/gduCmYww==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.168.tgz",
"integrity": "sha512-tvFGCGVX9tesGBzfpK008nRmv91pd3ToapDpJbjdwiFSLGWT9n+dc/qurwgxgv9VwWRvV8nWTc3zLRSFI8Lf2g==",
"cpu": [
"arm64"
],
@@ -123,9 +123,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.2.133.tgz",
"integrity": "sha512-juKz5zhWMYPClHWzraJ+CY/DoRfYfgaKsIQC0Tvln+scVJecEYt7tmvw+mmy2yQtPI66BqtoH12Aqnwyq5+RVw==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.168.tgz",
"integrity": "sha512-J5dZUCrwjz4+gY2dAds2rMDWWSdcMl9jEDpZIzKNCx0f0KlJfE1C7/+stOJaBv6tGj8d/mmzcfBCeNBS1KScgA==",
"cpu": [
"x64"
],
@@ -136,9 +136,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.2.133.tgz",
"integrity": "sha512-80agdXdE1/wCofiyiSGw1EINXv+xcjXaXkmAQgtAwhE22rtzjPh6D+Zm6sHRhyEdogj8PAVw6iAVRVV1xLYq6w==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.168.tgz",
"integrity": "sha512-K9fltcI0VJBr21z4t0iJ4aArFtiS9UwhmMGkxl1jAIM0jatGqiHHbbk+qaCB04va3U6vaflukgtmbpogSB36yQ==",
"cpu": [
"arm64"
],
@@ -149,9 +149,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.2.133.tgz",
"integrity": "sha512-lvB9C7mnGO1zZp6W90H7zcj11Qaw2Thcwcoov5cFRuC31NDuIZTdMHYMwXBqACep61aBX1fgawoY/+pq0Hptpw==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.168.tgz",
"integrity": "sha512-uOFtBJjntrYkXlHPRK+cTx1pCc37XxGQ/kN3KBK/KoIDcIrmU8DcX2WUw5IJHJqpG2Qjb7gZ5xAbcw3WTmduwQ==",
"cpu": [
"arm64"
],
@@ -162,9 +162,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.2.133.tgz",
"integrity": "sha512-nl9Law15GS90GEhddMaMuuSJXpRCHLOFm39Aqy5KxSqA6sdLXvi1kLEs2IIJ6XrUDTq0LnwDYACSTR21ybq3cg==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.168.tgz",
"integrity": "sha512-0Fw4ICZopwKbqNQo64HqkpJw5cqxOxFOV/w8GCS2hDsJ+0aogmK/B78t4iV6rFHIkC076Nm5UH14FP1eOIJ3OA==",
"cpu": [
"x64"
],
@@ -175,9 +175,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.2.133.tgz",
"integrity": "sha512-IShZIbuXLxHzjVcccKFZUYXtN6FwrHVlh3y5YDuQORLYSGSHtcUAfGAT1csGz7fTQ3ZvBfY8ApoWXMR8I65l8g==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.168.tgz",
"integrity": "sha512-JwYD7oxYlIVYDFhgj+QVHgWbJUVGMXsiecB6WQ5VS5u4OqUorivJPFYPaP469otMj4HFtkDupESB+7Zp4jMmUw==",
"cpu": [
"x64"
],
@@ -188,9 +188,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.2.133.tgz",
"integrity": "sha512-yiQqa9t5OLXsM2a49FS7evQyalEWPtAd5sv4LbymSlq/QrBqDymvgrxSwxHwaZan5JlNYoQKRlUW27lc2vTLQA==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.168.tgz",
"integrity": "sha512-NREaLXAxl23pEzoU7bmX6u8MibAYEY2t8XP5xtj/vMO6UwxFjT3YgGsTuQcnFP+1qsjWaOBCia2ZQnqI68Y0Ng==",
"cpu": [
"arm64"
],
@@ -201,9 +201,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.2.133.tgz",
"integrity": "sha512-tH6tviy+vYPoL6M7qGCg4gKLt3w5AxhmUN7yi8Rs/2AjMbYjPuM6F/G7T+4/CTg246k9gZxxA86Dxs95gQMv8A==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.168.tgz",
"integrity": "sha512-Ha3eivz2Wm4y3BR+Xpn/r+CBM8ARC6knYDLbjLqH/DxjKahr6WB60xQkBD1U6wchT6w7zfIQqQGbucuUOTTuew==",
"cpu": [
"x64"
],
@@ -213,6 +213,27 @@
"win32"
]
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.102.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.102.0.tgz",
"integrity": "sha512-cThh3KcPW3lzkFyTz1cjyhJvOVw45NkLMoowO2ZJ/76CBz44ADUon+NsjEc/PypAkARs72Xu8qxTnx6PAOTQUQ==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1",
"standardwebhooks": "^1.0.0"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@babel/cli": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz",
@@ -4971,6 +4992,16 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/@expo/eas-build-job/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@expo/eas-json": {
"version": "16.30.0",
"resolved": "https://registry.npmjs.org/@expo/eas-json/-/eas-json-16.30.0.tgz",
@@ -5164,6 +5195,16 @@
"dev": true,
"license": "0BSD"
},
"node_modules/@expo/eas-json/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@expo/env": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-1.0.7.tgz",
@@ -6392,6 +6433,16 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/@expo/steps/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@expo/sudo-prompt": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz",
@@ -11465,6 +11516,12 @@
"integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==",
"license": "CC0-1.0"
},
"node_modules/@stablelib/base64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
"integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
"license": "MIT"
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -12025,15 +12082,6 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/router-generator/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@tanstack/router-plugin": {
"version": "1.167.35",
"resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.167.35.tgz",
@@ -12086,15 +12134,6 @@
}
}
},
"node_modules/@tanstack/router-plugin/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@tanstack/router-utils": {
"version": "1.161.8",
"resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.8.tgz",
@@ -12444,15 +12483,6 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@tanstack/start-plugin-core/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@tanstack/start-server-core": {
"version": "1.167.30",
"resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.167.30.tgz",
@@ -13055,16 +13085,6 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.4"
}
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
@@ -14458,18 +14478,6 @@
"node": ">= 6.0.0"
}
},
"node_modules/agentkeepalive": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"license": "MIT",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/ai": {
"version": "5.0.78",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.78.tgz",
@@ -15223,6 +15231,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/at-least-node": {
@@ -16721,6 +16730,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
@@ -17579,6 +17589,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@@ -18217,6 +18228,16 @@
"node": ">= 14"
}
},
"node_modules/eas-cli/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -18683,6 +18704,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -21175,16 +21197,6 @@
"node": ">=0.10.0"
}
},
"node_modules/expo-module-scripts/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/expo-modules-autolinking": {
"version": "3.0.24",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz",
@@ -22329,6 +22341,12 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
"node_modules/fast-sha256": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
"integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
"license": "Unlicense"
},
"node_modules/fast-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
@@ -22812,6 +22830,7 @@
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -22824,12 +22843,6 @@
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
"license": "MIT"
},
"node_modules/formatly": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz",
@@ -22846,19 +22859,6 @@
"node": ">=18.3.0"
}
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"license": "MIT",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -23991,15 +23991,6 @@
"node": ">= 6"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/hyperlinker": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz",
@@ -26150,6 +26141,16 @@
"typescript": ">=5.0.4 <7"
}
},
"node_modules/knip/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/lan-network": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.1.7.tgz",
@@ -29012,26 +29013,6 @@
"node": "*"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -29628,51 +29609,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openai": {
"version": "4.104.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
},
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/openai/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/openai/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/opentype.js": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz",
@@ -33747,6 +33683,16 @@
"node": ">=8"
}
},
"node_modules/standardwebhooks": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
"integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
"license": "MIT",
"dependencies": {
"@stablelib/base64": "^1.0.0",
"fast-sha256": "^1.3.0"
}
},
"node_modules/stat-mode": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
@@ -36208,15 +36154,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -36869,9 +36806,9 @@
}
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -37028,7 +36965,7 @@
"react-native-worklets": "~0.8.3",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.6.0",
"zod": "^3.23.8",
"zod": "^4.1.8",
"zustand": "^5.0.9"
},
"devDependencies": {
@@ -37168,9 +37105,9 @@
}
},
"packages/app/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -37433,7 +37370,7 @@
"dependencies": {
"@getpaseo/protocol": "0.1.91-beta.2",
"@getpaseo/relay": "0.1.91-beta.2",
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "^20.9.0",
@@ -37442,9 +37379,9 @@
}
},
"packages/client/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -37460,7 +37397,7 @@
"electron-log": "^5.4.3",
"electron-updater": "^6.6.2",
"ws": "^8.14.2",
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "24.6.0",
@@ -37694,9 +37631,9 @@
}
},
"packages/desktop/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -37973,7 +37910,7 @@
"name": "@getpaseo/protocol",
"version": "0.1.91-beta.2",
"dependencies": {
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "^20.9.0",
@@ -37982,9 +37919,9 @@
}
},
"packages/protocol/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -38213,13 +38150,14 @@
"version": "0.1.91-beta.2",
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
"@anthropic-ai/claude-agent-sdk": "^0.3.168",
"@anthropic-ai/sdk": "^0.102.0",
"@getpaseo/client": "0.1.91-beta.2",
"@getpaseo/highlight": "0.1.91-beta.2",
"@getpaseo/protocol": "0.1.91-beta.2",
"@getpaseo/relay": "0.1.91-beta.2",
"@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1",
"@modelcontextprotocol/sdk": "^1.29.0",
"@opencode-ai/sdk": "1.14.46",
"@xterm/headless": "^6.0.0",
"ai": "5.0.78",
@@ -38231,7 +38169,7 @@
"lru-cache": "^11.5.1",
"mnemonic-id": "^3.2.7",
"node-pty": "1.2.0-beta.11",
"openai": "^4.20.0",
"openai": "^6.42.0",
"p-limit": "^7.3.0",
"p-memoize": "^8.0.0",
"pino": "^10.2.0",
@@ -38244,8 +38182,7 @@
"uuid": "^9.0.1",
"which": "^5.0.0",
"ws": "^8.14.2",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.25.1"
"zod": "^4.1.8"
},
"devDependencies": {
"@playwright/test": "^1.56.1",
@@ -38262,51 +38199,29 @@
}
},
"packages/server/node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.2.133",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.133.tgz",
"integrity": "sha512-teqnsqYB7LeZ7BYRQx66BSB+ykiswiVJyeEQ3chdQrTsVhz7lK47uWqZ70zD6vJIh4a5PgljFcZabTNFHjJcJA==",
"version": "0.3.168",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.168.tgz",
"integrity": "sha512-C7n9uS1fsAt9lFKw/ovsFNAlPI7UXE2uPQbOzzKLpDNjfFR5spthALhV49b01PtYWEzTo3W3OJfgCv6wooXtYg==",
"license": "SEE LICENSE IN README.md",
"dependencies": {
"@anthropic-ai/sdk": "^0.81.0",
"@modelcontextprotocol/sdk": "^1.29.0"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.133",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.133",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.133",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.133",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.133",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.133",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.133",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.133"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.168",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.168",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.168",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.168",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.168",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.168",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.168",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.168"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^4.0.0"
}
},
"packages/server/node_modules/@anthropic-ai/sdk": {
"version": "0.81.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.81.0.tgz",
"integrity": "sha512-D4K5PvEV6wPiRtVlVsJHIUhHAmOZ6IT/I9rKlTf84gR7GyyAurPJK7z9BOf/AZqC5d1DhYQGJNKRmV+q8dGhgw==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"packages/server/node_modules/@isaacs/ttlcache": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-2.1.4.tgz",
@@ -38730,6 +38645,24 @@
"node": ">= 0.6"
}
},
"packages/server/node_modules/openai": {
"version": "6.42.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.42.0.tgz",
"integrity": "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg==",
"license": "Apache-2.0",
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"packages/server/node_modules/p-limit": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz",
@@ -38979,9 +38912,9 @@
}
},
"packages/server/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
+1 -1
View File
@@ -104,7 +104,7 @@
"react-native-worklets": "~0.8.3",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.6.0",
"zod": "^3.23.8",
"zod": "^4.1.8",
"zustand": "^5.0.9"
},
"devDependencies": {
@@ -18,13 +18,13 @@ export interface FavoriteModelRow {
const providerPreferencesSchema = z.object({
model: z.string().optional(),
mode: z.string().optional(),
thinkingByModel: z.record(z.string()).optional(),
featureValues: z.record(z.unknown()).optional(),
thinkingByModel: z.record(z.string(), z.string()).optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
});
const formPreferencesSchema = z.object({
provider: z.string().optional(),
providerPreferences: z.record(providerPreferencesSchema).optional(),
providerPreferences: z.record(z.string(), providerPreferencesSchema).optional(),
favoriteModels: z
.array(
z.object({
+1 -1
View File
@@ -37,7 +37,7 @@
"dependencies": {
"@getpaseo/protocol": "0.1.91-beta.2",
"@getpaseo/relay": "0.1.91-beta.2",
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "^20.9.0",
+1 -1
View File
@@ -116,7 +116,7 @@ function parseSentFrame(
return z
.object({
type: z.literal("session"),
message: z.record(z.unknown()),
message: z.record(z.string(), z.unknown()),
})
.parse(JSON.parse(assertStr(data))).message;
}
+1 -1
View File
@@ -28,7 +28,7 @@
"electron-log": "^5.4.3",
"electron-updater": "^6.6.2",
"ws": "^8.14.2",
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "24.6.0",
@@ -359,8 +359,9 @@ describe("daemon-manager commands", () => {
expect(thrown).toBeInstanceOf(Error);
const message = thrown?.message ?? "";
const recentLogsLabel = message.match(/Recent logs \(([^)]*)\):/)?.[1];
expect(message).toContain("Daemon failed to start: exit code 1");
expect(message).toContain(`Recent logs (${mocks.paseoHome}/daemon.log):`);
expect(recentLogsLabel?.split(/[\\/]/).at(-1)).toBe("daemon.log");
expect(message).toContain("recent daemon failure");
expect(mocks.spawnProcess).toHaveBeenCalledWith(
"node",
+1 -1
View File
@@ -26,7 +26,7 @@
"test": "vitest run"
},
"dependencies": {
"zod": "^3.23.8"
"zod": "^4.1.8"
},
"devDependencies": {
"@types/node": "^20.9.0",
+168 -167
View File
@@ -182,7 +182,7 @@ const AgentSelectOptionSchema = z.object({
label: z.string(),
description: z.string().optional(),
isDefault: z.boolean().optional(),
metadata: z.record(z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export const AgentFeatureToggleSchema = z.object({
@@ -218,7 +218,7 @@ const AgentModelDefinitionSchema: z.ZodType<AgentModelDefinition> = z
label: z.string(),
description: z.string().optional(),
isDefault: z.boolean().optional(),
metadata: z.record(z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
thinkingOptions: z.array(AgentSelectOptionSchema).optional(),
defaultThinkingOptionId: z.string().optional(),
})
@@ -265,21 +265,21 @@ const McpStdioServerConfigSchema = z.object({
type: z.literal("stdio"),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
alwaysLoad: z.boolean().optional(),
});
const McpHttpServerConfigSchema = z.object({
type: z.literal("http"),
url: z.string(),
headers: z.record(z.string()).optional(),
headers: z.record(z.string(), z.string()).optional(),
alwaysLoad: z.boolean().optional(),
});
const McpSseServerConfigSchema = z.object({
type: z.literal("sse"),
url: z.string(),
headers: z.record(z.string()).optional(),
headers: z.record(z.string(), z.string()).optional(),
alwaysLoad: z.boolean().optional(),
});
@@ -295,7 +295,7 @@ const AgentSessionConfigSchema = z.object({
modeId: z.string().optional(),
model: z.string().optional(),
thinkingOptionId: z.string().optional(),
featureValues: z.record(z.unknown()).optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
title: z.string().trim().min(1).max(MAX_EXPLICIT_AGENT_TITLE_CHARS).optional().nullable(),
approvalPolicy: z.string().optional(),
sandboxMode: z.string().optional(),
@@ -303,16 +303,16 @@ const AgentSessionConfigSchema = z.object({
webSearch: z.boolean().optional(),
extra: z
.object({
codex: z.record(z.unknown()).optional(),
claude: z.record(z.unknown()).optional(),
codex: z.record(z.string(), z.unknown()).optional(),
claude: z.record(z.string(), z.unknown()).optional(),
})
.partial()
.optional(),
systemPrompt: z.string().optional(),
mcpServers: z.record(McpServerConfigSchema).optional(),
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
});
const AgentPermissionUpdateSchema = z.record(z.unknown());
const AgentPermissionUpdateSchema = z.record(z.string(), z.unknown());
const AgentPermissionActionSchema = z.object({
id: z.string(),
label: z.string(),
@@ -325,7 +325,7 @@ export const AgentPermissionResponseSchema: z.ZodType<AgentPermissionResponse> =
z.object({
behavior: z.literal("allow"),
selectedActionId: z.string().optional(),
updatedInput: z.record(z.unknown()).optional(),
updatedInput: z.record(z.string(), z.unknown()).optional(),
updatedPermissions: z.array(AgentPermissionUpdateSchema).optional(),
}),
z.object({
@@ -336,22 +336,18 @@ export const AgentPermissionResponseSchema: z.ZodType<AgentPermissionResponse> =
}),
]);
export const AgentPermissionRequestPayloadSchema: z.ZodType<
AgentPermissionRequest,
z.ZodTypeDef,
unknown
> = z.object({
export const AgentPermissionRequestPayloadSchema: z.ZodType<AgentPermissionRequest> = z.object({
id: z.string(),
provider: AgentProviderSchema,
name: z.string(),
kind: z.enum(["tool", "plan", "question", "mode", "other"]),
title: z.string().optional(),
description: z.string().optional(),
input: z.record(z.unknown()).optional(),
input: z.record(z.string(), z.unknown()).optional(),
detail: z.lazy(() => ToolCallDetailPayloadSchema).optional(),
suggestions: z.array(AgentPermissionUpdateSchema).optional(),
actions: z.array(AgentPermissionActionSchema).optional(),
metadata: z.record(z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
const UnknownValueSchema = z.union([
@@ -390,102 +386,101 @@ const WorktreeSetupDetailPayloadSchema = z.object({
truncated: z.boolean().optional(),
});
const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail, z.ZodTypeDef, unknown> =
z.discriminatedUnion("type", [
WorktreeSetupDetailPayloadSchema,
z.object({
type: z.literal("shell"),
command: z.string(),
cwd: z.string().optional(),
output: z.string().optional(),
exitCode: z.number().nullable().optional(),
}),
z.object({
type: z.literal("read"),
filePath: z.string(),
content: z.string().optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
z.object({
type: z.literal("edit"),
filePath: z.string(),
oldString: z.string().optional(),
newString: z.string().optional(),
unifiedDiff: z.string().optional(),
}),
z.object({
type: z.literal("write"),
filePath: z.string(),
content: z.string().optional(),
}),
z.object({
type: z.literal("search"),
query: z.string(),
toolName: z.enum(["search", "grep", "glob", "web_search"]).optional(),
content: z.string().optional(),
filePaths: z.array(z.string()).optional(),
webResults: z
.array(
z.object({
title: z.string(),
url: z.string(),
}),
)
.optional(),
annotations: z.array(z.string()).optional(),
numFiles: z.number().optional(),
numMatches: z.number().optional(),
durationMs: z.number().optional(),
durationSeconds: z.number().optional(),
truncated: z.boolean().optional(),
mode: z.enum(["content", "files_with_matches", "count"]).optional(),
}),
z.object({
type: z.literal("fetch"),
url: z.string(),
prompt: z.string().optional(),
result: z.string().optional(),
code: z.number().optional(),
codeText: z.string().optional(),
bytes: z.number().optional(),
durationMs: z.number().optional(),
}),
z.object({
type: z.literal("sub_agent"),
subAgentType: z.string().optional(),
description: z.string().optional(),
childSessionId: z.string().optional(),
log: z.string(),
// Compat cruft for clients <= 0.1.65-beta.3 that required this field. Producers still
// emit `[]`; nothing reads it. Drop the field (and the `[]` emissions) once those
// clients are no longer in the field.
actions: z
.array(
z.object({
index: z.number().int().positive(),
toolName: z.string(),
summary: z.string().optional(),
}),
)
.optional(),
}),
z.object({
type: z.literal("plain_text"),
label: z.string().optional(),
text: z.string().optional(),
icon: z.enum(TOOL_CALL_ICON_NAMES).optional(),
}),
z.object({
type: z.literal("plan"),
text: z.string(),
}),
z.object({
type: z.literal("unknown"),
input: UnknownValueSchema,
output: UnknownValueSchema,
}),
]);
const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail> = z.discriminatedUnion("type", [
WorktreeSetupDetailPayloadSchema,
z.object({
type: z.literal("shell"),
command: z.string(),
cwd: z.string().optional(),
output: z.string().optional(),
exitCode: z.number().nullable().optional(),
}),
z.object({
type: z.literal("read"),
filePath: z.string(),
content: z.string().optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
z.object({
type: z.literal("edit"),
filePath: z.string(),
oldString: z.string().optional(),
newString: z.string().optional(),
unifiedDiff: z.string().optional(),
}),
z.object({
type: z.literal("write"),
filePath: z.string(),
content: z.string().optional(),
}),
z.object({
type: z.literal("search"),
query: z.string(),
toolName: z.enum(["search", "grep", "glob", "web_search"]).optional(),
content: z.string().optional(),
filePaths: z.array(z.string()).optional(),
webResults: z
.array(
z.object({
title: z.string(),
url: z.string(),
}),
)
.optional(),
annotations: z.array(z.string()).optional(),
numFiles: z.number().optional(),
numMatches: z.number().optional(),
durationMs: z.number().optional(),
durationSeconds: z.number().optional(),
truncated: z.boolean().optional(),
mode: z.enum(["content", "files_with_matches", "count"]).optional(),
}),
z.object({
type: z.literal("fetch"),
url: z.string(),
prompt: z.string().optional(),
result: z.string().optional(),
code: z.number().optional(),
codeText: z.string().optional(),
bytes: z.number().optional(),
durationMs: z.number().optional(),
}),
z.object({
type: z.literal("sub_agent"),
subAgentType: z.string().optional(),
description: z.string().optional(),
childSessionId: z.string().optional(),
log: z.string(),
// Compat cruft for clients <= 0.1.65-beta.3 that required this field. Producers still
// emit `[]`; nothing reads it. Drop the field (and the `[]` emissions) once those
// clients are no longer in the field.
actions: z
.array(
z.object({
index: z.number().int().positive(),
toolName: z.string(),
summary: z.string().optional(),
}),
)
.optional(),
}),
z.object({
type: z.literal("plain_text"),
label: z.string().optional(),
text: z.string().optional(),
icon: z.enum(TOOL_CALL_ICON_NAMES).optional(),
}),
z.object({
type: z.literal("plan"),
text: z.string(),
}),
z.object({
type: z.literal("unknown"),
input: UnknownValueSchema,
output: UnknownValueSchema,
}),
]);
const ToolCallBasePayloadSchema = z.object({
type: z.literal("tool_call"),
@@ -515,51 +510,49 @@ const ToolCallCanceledPayloadSchema = ToolCallBasePayloadSchema.extend({
error: z.null(),
});
const ToolCallTimelineItemPayloadSchema: z.ZodType<ToolCallTimelineItem, z.ZodTypeDef, unknown> =
z.union([
ToolCallRunningPayloadSchema,
ToolCallCompletedPayloadSchema,
ToolCallFailedPayloadSchema,
ToolCallCanceledPayloadSchema,
]);
const ToolCallTimelineItemPayloadSchema: z.ZodType<ToolCallTimelineItem> = z.union([
ToolCallRunningPayloadSchema,
ToolCallCompletedPayloadSchema,
ToolCallFailedPayloadSchema,
ToolCallCanceledPayloadSchema,
]);
export const AgentTimelineItemPayloadSchema: z.ZodType<AgentTimelineItem, z.ZodTypeDef, unknown> =
z.union([
z.object({
type: z.literal("user_message"),
text: z.string(),
messageId: z.string().optional(),
}),
z.object({
type: z.literal("assistant_message"),
text: z.string(),
messageId: z.string().optional(),
}),
z.object({
type: z.literal("reasoning"),
text: z.string(),
}),
ToolCallTimelineItemPayloadSchema,
z.object({
type: z.literal("todo"),
items: z.array(
z.object({
text: z.string(),
completed: z.boolean(),
}),
),
}),
z.object({
type: z.literal("error"),
message: z.string(),
}),
z.object({
type: z.literal("compaction"),
status: z.enum(["loading", "completed"]),
trigger: z.enum(["auto", "manual"]).optional(),
preTokens: z.number().optional(),
}),
]);
export const AgentTimelineItemPayloadSchema: z.ZodType<AgentTimelineItem> = z.union([
z.object({
type: z.literal("user_message"),
text: z.string(),
messageId: z.string().optional(),
}),
z.object({
type: z.literal("assistant_message"),
text: z.string(),
messageId: z.string().optional(),
}),
z.object({
type: z.literal("reasoning"),
text: z.string(),
}),
ToolCallTimelineItemPayloadSchema,
z.object({
type: z.literal("todo"),
items: z.array(
z.object({
text: z.string(),
completed: z.boolean(),
}),
),
}),
z.object({
type: z.literal("error"),
message: z.string(),
}),
z.object({
type: z.literal("compaction"),
status: z.enum(["loading", "completed"]),
trigger: z.enum(["auto", "manual"]).optional(),
preTokens: z.number().optional(),
}),
]);
export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
z.object({
@@ -734,7 +727,7 @@ export const AudioPlayedMessageSchema = z.object({
});
const AgentDirectoryFilterSchema = z.object({
labels: z.record(z.string()).optional(),
labels: z.record(z.string(), z.string()).optional(),
projectKeys: z.array(z.string()).optional(),
statuses: z.array(AgentStatusSchema).optional(),
includeArchived: z.boolean().optional(),
@@ -765,7 +758,7 @@ export const UpdateAgentRequestMessageSchema = z.object({
type: z.literal("update_agent_request"),
agentId: z.string(),
name: z.string().optional(),
labels: z.record(z.string()).optional(),
labels: z.record(z.string(), z.string()).optional(),
requestId: z.string(),
});
@@ -1108,18 +1101,18 @@ export type CreateAgentWorktreeTarget = z.infer<typeof CreateAgentWorktreeTarget
export const CreateAgentRequestMessageSchema = z.object({
type: z.literal("create_agent_request"),
config: AgentSessionConfigSchema,
env: z.record(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
workspaceId: z.string().optional(),
worktreeName: z.string().optional(),
initialPrompt: z.string().optional(),
clientMessageId: z.string().optional(),
outputSchema: z.record(z.unknown()).optional(),
outputSchema: z.record(z.string(), z.unknown()).optional(),
images: z.array(ImageAttachmentSchema).optional(),
attachments: AgentAttachmentsSchema,
git: GitSetupOptionsSchema.optional(),
worktree: CreateAgentWorktreeTargetSchema.optional(),
autoArchive: z.boolean().optional(),
labels: z.record(z.string()).default({}),
labels: z.record(z.string(), z.string()).default({}),
requestId: z.string(),
});
@@ -1175,7 +1168,7 @@ export const ImportAgentRequestMessageSchema = z.object({
sessionId: z.string().optional(),
providerHandleId: z.string().optional(),
cwd: z.string().optional(),
labels: z.record(z.string()).optional(),
labels: z.record(z.string(), z.string()).optional(),
requestId: z.string(),
});
@@ -1726,7 +1719,7 @@ const ListCommandsDraftConfigSchema = z.object({
modeId: z.string().optional(),
model: z.string().optional(),
thinkingOptionId: z.string().optional(),
featureValues: z.record(z.unknown()).optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
});
export const ListProviderFeaturesRequestMessageSchema = z.object({
@@ -1981,7 +1974,7 @@ export const ActivityLogPayloadSchema = z.object({
timestamp: z.coerce.date(),
type: z.enum(["transcript", "assistant", "tool_call", "tool_result", "error", "system"]),
content: z.string(),
metadata: z.record(z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export const ActivityLogMessageSchema = z.object({
@@ -3256,7 +3249,15 @@ export const PullRequestTimelineResponseSchema = z.object({
githubFeaturesEnabled: z.boolean().optional().default(true),
})
.optional()
.default({}),
.default({
cwd: "",
prNumber: null,
items: [],
truncated: false,
error: null,
requestId: "",
githubFeaturesEnabled: true,
}),
});
export const CheckoutSwitchBranchResponseSchema = z.object({
@@ -33,6 +33,23 @@ describe("paseo config schema", () => {
});
});
it("parses worktree config when only one lifecycle hook is present", () => {
expect(
PaseoConfigSchema.parse({
worktree: {
setup: "npm install",
terminals: [{ name: "Dev", command: "npm run dev" }],
},
}),
).toEqual({
worktree: {
setup: ["npm install"],
teardown: [],
terminals: [{ name: "Dev", command: "npm run dev" }],
},
});
});
it("parses all metadata generation instruction entries", () => {
expect(
PaseoConfigSchema.parse({
+2 -2
View File
@@ -56,8 +56,8 @@ export const PaseoConfigRawSchema = z
.passthrough();
export const WorktreeConfigSchema = PaseoWorktreeConfigRawSchema.extend({
setup: z.unknown().transform(normalizeLifecycleCommands),
teardown: z.unknown().transform(normalizeLifecycleCommands),
setup: z.unknown().optional().transform(normalizeLifecycleCommands),
teardown: z.unknown().optional().transform(normalizeLifecycleCommands),
})
.passthrough()
.catch({ setup: [], teardown: [] });
+4 -4
View File
@@ -24,7 +24,7 @@ export const ProviderCommandSchema = z.discriminatedUnion("mode", [
export const ProviderRuntimeSettingsSchema = z.object({
command: ProviderCommandSchema.optional(),
env: z.record(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
disallowedTools: z.array(z.string()).optional(),
});
@@ -48,7 +48,7 @@ export const ProviderOverrideSchema = z.object({
label: z.string().optional(),
description: z.string().optional(),
command: z.array(z.string().min(1)).min(1).optional(),
env: z.record(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
models: z.array(ProviderProfileModelSchema).optional(),
additionalModels: z.array(ProviderProfileModelSchema).optional(),
disallowedTools: z.array(z.string()).optional(),
@@ -60,7 +60,7 @@ const BUILTIN_PROVIDER_IDS = ["claude", "codex", "copilot", "opencode", "pi"] as
const PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
export const ProviderOverridesSchema = z
.record(ProviderOverrideSchema)
.record(z.string(), ProviderOverrideSchema)
.superRefine((providers, ctx) => {
const builtinProviderIdSet = new Set<string>(BUILTIN_PROVIDER_IDS);
const validExtendsValues = new Set<string>([...BUILTIN_PROVIDER_IDS, "acp"]);
@@ -110,7 +110,7 @@ export const ProviderOverridesSchema = z
});
export const AgentProviderRuntimeSettingsMapSchema = z
.record(ProviderRuntimeSettingsSchema)
.record(z.string(), ProviderRuntimeSettingsSchema)
.superRefine((providers, ctx) => {
for (const providerId of Object.keys(providers)) {
const parsedProviderId = AgentProviderSchema.safeParse(providerId);
+26 -5
View File
@@ -1,16 +1,16 @@
import { describe, expect, test } from "vitest";
import { describe, expect, it } from "vitest";
import { ScheduleCadenceSchema } from "./types.js";
import { ScheduleCadenceSchema, ScheduleRunSchema, ScheduleTargetSchema } from "./types.js";
describe("ScheduleCadenceSchema", () => {
test("accepts existing UTC cron cadence without a time zone", () => {
describe("schedule schemas", () => {
it("accepts existing UTC cron cadence without a time zone", () => {
expect(ScheduleCadenceSchema.parse({ type: "cron", expression: "0 9 * * *" })).toEqual({
type: "cron",
expression: "0 9 * * *",
});
});
test("accepts timezone-aware cron cadence", () => {
it("accepts timezone-aware cron cadence", () => {
expect(
ScheduleCadenceSchema.parse({
type: "cron",
@@ -23,4 +23,25 @@ describe("ScheduleCadenceSchema", () => {
timezone: "America/New_York",
});
});
it("accepts legacy uuid-shaped agent ids with version 0", () => {
const legacyAgentId = "00000000-0000-0000-0000-000000000001";
expect(ScheduleTargetSchema.parse({ type: "agent", agentId: legacyAgentId })).toEqual({
type: "agent",
agentId: legacyAgentId,
});
expect(
ScheduleRunSchema.parse({
id: "run-1",
scheduledFor: "2026-01-01T00:00:00.000Z",
startedAt: "2026-01-01T00:00:00.000Z",
endedAt: null,
status: "succeeded",
agentId: legacyAgentId,
output: null,
error: null,
}).agentId,
).toBe(legacyAgentId);
});
});
+13 -6
View File
@@ -17,10 +17,17 @@ export const ScheduleCadenceSchema = z.discriminatedUnion("type", [
]);
export type ScheduleCadence = z.infer<typeof ScheduleCadenceSchema>;
const LegacyUuidSchema = z
.string()
.regex(
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
"Invalid UUID",
);
export const ScheduleTargetSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("agent"),
agentId: z.string().uuid(),
agentId: LegacyUuidSchema,
}),
z.object({
type: z.literal("new-agent"),
@@ -35,16 +42,16 @@ export const ScheduleTargetSchema = z.discriminatedUnion("type", [
sandboxMode: z.string().trim().min(1).optional(),
networkAccess: z.boolean().optional(),
webSearch: z.boolean().optional(),
featureValues: z.record(z.unknown()).optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
extra: z
.object({
codex: z.record(z.unknown()).optional(),
claude: z.record(z.unknown()).optional(),
codex: z.record(z.string(), z.unknown()).optional(),
claude: z.record(z.string(), z.unknown()).optional(),
})
.partial()
.optional(),
systemPrompt: z.string().optional(),
mcpServers: z.record(z.unknown()).optional(),
mcpServers: z.record(z.string(), z.unknown()).optional(),
}),
}),
]);
@@ -56,7 +63,7 @@ export const ScheduleRunSchema = z.object({
startedAt: z.string(),
endedAt: z.string().nullable(),
status: z.enum(["running", "succeeded", "failed"]),
agentId: z.string().uuid().nullable(),
agentId: LegacyUuidSchema.nullable(),
output: z.string().nullable(),
error: z.string().nullable(),
});
+5 -5
View File
@@ -58,13 +58,14 @@
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
"@anthropic-ai/claude-agent-sdk": "^0.3.168",
"@anthropic-ai/sdk": "^0.102.0",
"@getpaseo/client": "0.1.91-beta.2",
"@getpaseo/highlight": "0.1.91-beta.2",
"@getpaseo/protocol": "0.1.91-beta.2",
"@getpaseo/relay": "0.1.91-beta.2",
"@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1",
"@modelcontextprotocol/sdk": "^1.29.0",
"@opencode-ai/sdk": "1.14.46",
"@xterm/headless": "^6.0.0",
"ai": "5.0.78",
@@ -76,7 +77,7 @@
"lru-cache": "^11.5.1",
"mnemonic-id": "^3.2.7",
"node-pty": "1.2.0-beta.11",
"openai": "^4.20.0",
"openai": "^6.42.0",
"p-limit": "^7.3.0",
"p-memoize": "^8.0.0",
"pino": "^10.2.0",
@@ -89,8 +90,7 @@
"uuid": "^9.0.1",
"which": "^5.0.0",
"ws": "^8.14.2",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.25.1"
"zod": "^4.1.8"
},
"devDependencies": {
"@playwright/test": "^1.56.1",
@@ -2,7 +2,7 @@ import { fileURLToPath } from "node:url";
import path from "node:path";
import fs from "node:fs";
import { zodToJsonSchema } from "zod-to-json-schema";
import { z } from "zod";
import { PersistedConfigSchema } from "../src/server/persisted-config.js";
const __filename = fileURLToPath(import.meta.url);
@@ -13,9 +13,10 @@ function main() {
const outPath = path.join(repoRoot, "packages/website/public/schemas/paseo.config.v1.json");
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const schema = zodToJsonSchema(PersistedConfigSchema, {
name: "PaseoConfigV1",
});
const schema = {
...z.toJSONSchema(PersistedConfigSchema, { target: "draft-7" }),
title: "PaseoConfigV1",
};
fs.writeFileSync(outPath, JSON.stringify(schema, null, 2) + "\n", "utf8");
process.stdout.write(`Wrote ${outPath}\n`);
@@ -28,7 +28,6 @@ import type {
PersistedAgentDescriptor,
} from "./agent-sdk-types.js";
import type { ProviderDefinition } from "./provider-registry.js";
import { PaseoToolingRuntime } from "./paseo-tooling/runtime.js";
interface Deferred<T> {
promise: Promise<T>;
@@ -523,20 +522,7 @@ test("createAgent injects daemon append system prompt at runtime only", async ()
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
class CaptureClient extends TestAgentClient {
lastLaunchContext: AgentLaunchContext | null = null;
override async resumeSession(
handle: AgentPersistenceHandle,
config?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
this.lastLaunchContext = launchContext ?? null;
return super.resumeSession(handle, config, launchContext);
}
}
const client = new CaptureClient();
const client = new TestAgentClient();
const manager = new AgentManager({
clients: {
codex: client,
@@ -565,20 +551,7 @@ test("daemon append system prompt is injected into Pi configs", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
class CaptureClient extends TestAgentClient {
lastLaunchContext: AgentLaunchContext | null = null;
override async resumeSession(
handle: AgentPersistenceHandle,
config?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
this.lastLaunchContext = launchContext ?? null;
return super.resumeSession(handle, config, launchContext);
}
}
const client = new CaptureClient();
const client = new TestAgentClient();
const manager = new AgentManager({
clients: {
pi: client as unknown as AgentClient,
@@ -975,38 +948,28 @@ test("createAgent passes persistSession to provider create options", async () =>
rmSync(workdir, { recursive: true, force: true });
});
test("createAgent passes internal paseo tooling only through runtime launch context", async () => {
test("createAgent injects paseo MCP server only into provider launch config", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
class CaptureClient extends TestAgentClient {
lastConfig: AgentSessionConfig | null = null;
lastLaunchContext: AgentLaunchContext | null = null;
override async createSession(
config: AgentSessionConfig,
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
override async createSession(config: AgentSessionConfig): Promise<AgentSession> {
this.lastConfig = config;
this.lastLaunchContext = launchContext ?? null;
return new TestAgentSession(config);
}
}
const client = new CaptureClient();
const paseoToolingRuntime = new PaseoToolingRuntime();
paseoToolingRuntime.setEndpoints({
mcpBaseUrl: "http://127.0.0.1:6767/mcp/agents",
httpBaseUrl: "http://127.0.0.1:6767",
});
const manager = new AgentManager({
clients: {
codex: client,
},
registry: storage,
logger,
paseoToolingRuntime,
mcpBaseUrl: "http://127.0.0.1:6767/mcp/agents",
idFactory: () => "00000000-0000-4000-8000-000000000103",
});
@@ -1028,15 +991,15 @@ test("createAgent passes internal paseo tooling only through runtime launch cont
},
});
expect(client.lastConfig?.mcpServers).toEqual({
paseo: {
type: "http",
url: `http://127.0.0.1:6767/mcp/agents?callerAgentId=${snapshot.id}`,
},
custom: {
type: "stdio",
command: "custom-mcp",
},
});
expect(client.lastLaunchContext?.paseoTooling?.mcpUrl).toBe(
`http://127.0.0.1:6767/mcp/agents?callerAgentId=${snapshot.id}`,
);
expect(client.lastLaunchContext?.paseoTooling?.httpBaseUrl).toBe("http://127.0.0.1:6767");
const stored = await storage.get(snapshot.id);
expect(stored?.config?.mcpServers).toEqual({
@@ -1047,36 +1010,18 @@ test("createAgent passes internal paseo tooling only through runtime launch cont
});
});
test("resumeAgentFromPersistence strips stored internal paseo MCP and passes current runtime tooling", async () => {
test("resumeAgentFromPersistence replaces stored internal paseo MCP with current runtime URL", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
class CaptureClient extends TestAgentClient {
lastLaunchContext: AgentLaunchContext | null = null;
override async resumeSession(
handle: AgentPersistenceHandle,
config?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
this.lastLaunchContext = launchContext ?? null;
return super.resumeSession(handle, config, launchContext);
}
}
const client = new CaptureClient();
const paseoToolingRuntime = new PaseoToolingRuntime();
paseoToolingRuntime.setEndpoints({
mcpBaseUrl: "http://127.0.0.1:6768/mcp/agents",
httpBaseUrl: "http://127.0.0.1:6768",
});
const client = new TestAgentClient();
const manager = new AgentManager({
clients: {
codex: client,
},
registry: storage,
logger,
paseoToolingRuntime,
mcpBaseUrl: "http://127.0.0.1:6768/mcp/agents",
idFactory: () => "00000000-0000-4000-8000-000000000105",
});
const handle: AgentPersistenceHandle = {
@@ -1102,14 +1047,15 @@ test("resumeAgentFromPersistence strips stored internal paseo MCP and passes cur
});
expect(client.resumeOverrides[0]?.mcpServers).toEqual({
paseo: {
type: "http",
url: `http://127.0.0.1:6768/mcp/agents?callerAgentId=${snapshot.id}`,
},
custom: {
type: "stdio",
command: "custom-mcp",
},
});
expect(client.lastLaunchContext?.paseoTooling?.mcpUrl).toBe(
`http://127.0.0.1:6768/mcp/agents?callerAgentId=${snapshot.id}`,
);
expect(snapshot.config.mcpServers).toEqual({
custom: {
type: "stdio",
@@ -1173,6 +1119,7 @@ test("createAgent preserves a user-provided paseo MCP config", async () => {
},
registry: storage,
logger,
mcpBaseUrl: "http://127.0.0.1:6767/mcp/agents",
idFactory: () => "00000000-0000-4000-8000-000000000104",
});
@@ -57,8 +57,7 @@ import { getAgentProviderDefinition } from "@getpaseo/protocol/provider-manifest
import { IMPORTABLE_PROVIDERS } from "./provider-registry.js";
import { invokeRewindCapability, type RewindMode } from "./rewind/rewind.js";
import { isSystemInjectedEnvelope } from "./agent-prompt.js";
import { stripInternalPaseoMcpServer } from "./runtime-mcp-config.js";
import type { PaseoToolingRuntime } from "./paseo-tooling/runtime.js";
import { stripInternalPaseoMcpServer, withRuntimePaseoMcpServer } from "./runtime-mcp-config.js";
const RELOAD_SESSION_CLOSE_TIMEOUT_MS = 3_000;
const INTERRUPT_SESSION_TIMEOUT_MS = 2_000;
@@ -190,7 +189,7 @@ export interface AgentManagerOptions {
onAgentAttention?: AgentAttentionCallback;
durableTimelineStore?: AgentTimelineStore;
terminalManager?: TerminalManager | null;
paseoToolingRuntime?: PaseoToolingRuntime;
mcpBaseUrl?: string;
appendSystemPrompt?: string;
agentStreamCoalesceWindowMs?: number;
rescueTimeouts?: AgentManagerRescueTimeouts;
@@ -433,7 +432,7 @@ export class AgentManager {
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
private readonly backgroundTasks = new Set<Promise<void>>();
private readonly agentStreamCoalescer: AgentStreamCoalescer;
private readonly paseoToolingRuntime: PaseoToolingRuntime | null;
private mcpBaseUrl: string | null;
private appendSystemPrompt: string;
private onAgentAttention?: AgentAttentionCallback;
private onAgentArchived?: AgentArchivedCallback;
@@ -445,7 +444,7 @@ export class AgentManager {
this.registry = options?.registry;
this.durableTimelineStore = options?.durableTimelineStore;
this.onAgentAttention = options?.onAgentAttention;
this.paseoToolingRuntime = options?.paseoToolingRuntime ?? null;
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
this.appendSystemPrompt = options.appendSystemPrompt ?? "";
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
this.rescueTimeouts = {
@@ -501,6 +500,10 @@ export class AgentManager {
this.onAgentArchived = callback;
}
setMcpBaseUrl(url: string | null): void {
this.mcpBaseUrl = url;
}
setAppendSystemPrompt(prompt: string | null | undefined): void {
this.appendSystemPrompt = prompt ?? "";
}
@@ -3457,10 +3460,16 @@ export class AgentManager {
private async prepareSessionConfig(
config: AgentSessionConfig,
_agentId: string,
agentId: string,
): Promise<PreparedSessionConfig> {
const storedConfig = await this.normalizeConfig(stripInternalPaseoMcpServer(config));
const launchConfig = this.applyDaemonAppendSystemPrompt(storedConfig);
const launchConfig = this.applyDaemonAppendSystemPrompt(
withRuntimePaseoMcpServer({
config: storedConfig,
agentId,
mcpBaseUrl: this.mcpBaseUrl,
}),
);
return { storedConfig, launchConfig };
}
@@ -3480,7 +3489,6 @@ export class AgentManager {
private buildLaunchContext(agentId: string, env?: Record<string, string>): AgentLaunchContext {
return {
agentId,
paseoTooling: this.paseoToolingRuntime?.createLaunchContext(agentId),
env: {
...env,
PASEO_AGENT_ID: agentId,
@@ -1,5 +1,4 @@
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import Ajv, { type ErrorObject, type Options as AjvOptions } from "ajv";
import type { AgentProvider, AgentSessionConfig } from "./agent-sdk-types.js";
import type { AgentManager } from "./agent-manager.js";
@@ -111,11 +110,10 @@ function isZodSchema(value: unknown): value is z.ZodTypeAny {
}
function buildZodValidator<T>(schema: z.ZodTypeAny, schemaName: string): SchemaValidator<T> {
const zodToJsonSchemaAny = zodToJsonSchema as unknown as (
input: z.ZodTypeAny,
name?: string,
) => JsonSchema;
const jsonSchema = zodToJsonSchemaAny(schema, schemaName);
const jsonSchema = {
...z.toJSONSchema(schema, { target: "draft-7" }),
title: schemaName,
} as JsonSchema;
return {
jsonSchema,
validate: (value) => {
@@ -546,23 +546,9 @@ export interface AgentSessionConfig {
internal?: boolean;
}
export interface PaseoToolingProviderSessionRef {
provider: AgentProvider;
sessionId: string;
}
export interface PaseoToolingLaunchContext {
agentId: string;
mcpUrl: string | null;
httpBaseUrl: string | null;
token: string;
bindProviderSession(ref: PaseoToolingProviderSessionRef): () => void;
}
export interface AgentLaunchContext {
agentId?: string;
env?: Record<string, string>;
paseoTooling?: PaseoToolingLaunchContext;
}
export interface AgentCreateSessionOptions {
@@ -14,10 +14,10 @@ const SERIALIZABLE_CONFIG_SCHEMA = z
modeId: z.string().nullable().optional(),
model: z.string().nullable().optional(),
thinkingOptionId: z.string().nullable().optional(),
featureValues: z.record(z.unknown()).nullable().optional(),
extra: z.record(z.any()).nullable().optional(),
featureValues: z.record(z.string(), z.unknown()).nullable().optional(),
extra: z.record(z.string(), z.any()).nullable().optional(),
systemPrompt: z.string().nullable().optional(),
mcpServers: z.record(z.any()).nullable().optional(),
mcpServers: z.record(z.string(), z.any()).nullable().optional(),
})
.nullable()
.optional();
@@ -27,7 +27,7 @@ const PERSISTENCE_HANDLE_SCHEMA = z
provider: z.string(),
sessionId: z.string(),
nativeHandle: z.any().optional(),
metadata: z.record(z.any()).optional(),
metadata: z.record(z.string(), z.any()).optional(),
})
.nullable()
.optional();
@@ -41,7 +41,7 @@ const STORED_AGENT_SCHEMA = z.object({
lastActivityAt: z.string().optional(),
lastUserMessageAt: z.string().nullable().optional(),
title: z.string().nullable().optional(),
labels: z.record(z.string()).default({}),
labels: z.record(z.string(), z.string()).default({}),
lastStatus: AgentStatusSchema.default("closed"),
lastModeId: z.string().nullable().optional(),
config: SERIALIZABLE_CONFIG_SCHEMA,
@@ -52,7 +52,7 @@ const STORED_AGENT_SCHEMA = z.object({
model: z.string().nullable().optional(),
thinkingOptionId: z.string().nullable().optional(),
modeId: z.string().nullable().optional(),
extra: z.record(z.unknown()).optional(),
extra: z.record(z.string(), z.unknown()).optional(),
})
.optional(),
features: z.array(AgentFeatureSchema).optional(),
@@ -31,7 +31,7 @@ function str(val: unknown): string {
}
function recordArr(val: unknown): StructuredContent[] {
return z.array(z.record(z.unknown())).parse(val);
return z.array(z.record(z.string(), z.unknown())).parse(val);
}
function expectAgentFeatureValue(snapshot: StructuredContent, featureId: string, value: unknown) {
@@ -355,7 +355,7 @@ describe("Suite A: Core Fixes", () => {
expect(internalSnapshot?.config.featureValues).toEqual({ test_feature: true });
const status = await callToolStructured(topLevelClient, "get_agent_status", { agentId });
const snapshot = z.record(z.unknown()).parse(status.snapshot);
const snapshot = z.record(z.string(), z.unknown()).parse(status.snapshot);
expectAgentFeatureValue(snapshot, "test_feature", true);
} finally {
await archiveAgentIfPresent(agentId);
@@ -373,7 +373,7 @@ describe("Suite A: Core Fixes", () => {
expect(internalSnapshot?.config.featureValues).toEqual({ test_feature: true });
const status = await callToolStructured(topLevelClient, "get_agent_status", { agentId });
const snapshot = z.record(z.unknown()).parse(status.snapshot);
const snapshot = z.record(z.string(), z.unknown()).parse(status.snapshot);
expectAgentFeatureValue(snapshot, "test_feature", true);
} finally {
await archiveAgentIfPresent(agentId);
@@ -393,7 +393,7 @@ describe("Suite A: Core Fixes", () => {
expect(internalSnapshot?.config.featureValues).toEqual({ test_feature: true });
const status = await callToolStructured(topLevelClient, "get_agent_status", { agentId });
const snapshot = z.record(z.unknown()).parse(status.snapshot);
const snapshot = z.record(z.string(), z.unknown()).parse(status.snapshot);
expectAgentFeatureValue(snapshot, "test_feature", true);
} finally {
await archiveAgentIfPresent(agentId);
@@ -6,7 +6,6 @@ import { join, resolve as resolvePath } from "node:path";
import { tmpdir } from "node:os";
import Ajv from "ajv";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { createTestLogger } from "../../test-utils/test-logger.js";
import { createAgentMcpServer } from "./mcp-server.js";
@@ -112,7 +111,7 @@ async function invokeToolWithParsedInput(
function expectOutputSchemaAccepts(tool: RegisteredMcpTool, data: unknown): void {
expect(tool.outputSchema).toBeDefined();
const jsonSchema = zodToJsonSchema(tool.outputSchema as z.ZodTypeAny);
const jsonSchema = z.toJSONSchema(tool.outputSchema as z.ZodTypeAny, { target: "draft-7" });
const ajv = new Ajv({ allErrors: true, strict: false });
const validate = ajv.compile(jsonSchema);
expect(validate(data), JSON.stringify(validate.errors, null, 2)).toBe(true);
@@ -121,7 +120,7 @@ function expectOutputSchemaAccepts(tool: RegisteredMcpTool, data: unknown): void
function agentsOf(response: {
structuredContent: LooseStructuredContent;
}): Array<Record<string, unknown>> {
return z.array(z.record(z.unknown())).parse(response.structuredContent.agents);
return z.array(z.record(z.string(), z.unknown())).parse(response.structuredContent.agents);
}
type AgentManagerSpies = ReturnType<typeof buildAgentManagerSpies>;
@@ -3205,7 +3204,7 @@ describe("agent snapshot MCP serialization", () => {
const tool = registeredTool(server, "list_agents");
const response = await tool.handler({});
const structured = z
.object({ agents: z.array(z.record(z.unknown())) })
.object({ agents: z.array(z.record(z.string(), z.unknown())) })
.parse(response.structuredContent);
expect(structured).toEqual({
@@ -3317,7 +3316,7 @@ describe("agent snapshot MCP serialization", () => {
});
const tool = registeredTool(server, "get_agent_status");
const response = await tool.handler({ agentId: "full-detail-agent" });
const snapshot = z.record(z.unknown()).parse(response.structuredContent.snapshot);
const snapshot = z.record(z.string(), z.unknown()).parse(response.structuredContent.snapshot);
const parsed = AgentSnapshotPayloadSchema.safeParse(snapshot);
if (!parsed.success) {
File diff suppressed because it is too large Load Diff
@@ -1,101 +0,0 @@
import type express from "express";
import type { Logger } from "pino";
import { z } from "zod";
import type { AgentProvider } from "../agent-sdk-types.js";
import type { PaseoToolRegistry } from "./registry.js";
import type { PaseoToolingRuntime } from "./runtime.js";
const ToolExecuteBodySchema = z.object({
provider: z.string().min(1),
sessionId: z.string().min(1),
tool: z.string().min(1),
input: z.unknown().optional(),
});
export interface MountPaseoToolingHttpAdapterOptions {
app: express.Express;
registry: PaseoToolRegistry;
runtime: PaseoToolingRuntime;
logger: Logger;
}
export function mountPaseoToolingHttpAdapter(options: MountPaseoToolingHttpAdapterOptions): void {
const { app, registry, runtime, logger } = options;
const requireAuth: express.RequestHandler = (req, res, next) => {
if (req.header("authorization") === `Bearer ${runtime.token}`) {
next();
return;
}
res.status(401).json({ error: "Unauthorized" });
};
app.get("/api/paseo-tooling/manifest", requireAuth, (_req, res) => {
res.json({ tools: registry.listManifest() });
});
const runExecuteRequest = async (req: express.Request, res: express.Response): Promise<void> => {
const parsed = ToolExecuteBodySchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.message });
return;
}
const callerAgentId = runtime.resolveProviderSession({
provider: parsed.data.provider as AgentProvider,
sessionId: parsed.data.sessionId,
});
if (!callerAgentId) {
res.status(404).json({ error: "Provider session is not bound to a Paseo agent" });
return;
}
try {
const result = await registry.executeTool({
name: parsed.data.tool,
input: parsed.data.input ?? {},
callerAgentId,
});
res.json({ output: formatPaseoToolResult(result) });
} catch (err) {
logger.error(
{
err,
provider: parsed.data.provider,
sessionId: parsed.data.sessionId,
tool: parsed.data.tool,
},
"Paseo tooling HTTP execution failed",
);
res.status(500).json({ error: err instanceof Error ? err.message : "Tool execution failed" });
}
};
app.post("/api/paseo-tooling/execute", requireAuth, (req, res) => {
void runExecuteRequest(req, res);
});
}
function formatPaseoToolResult(result: unknown): unknown {
if (!result || typeof result !== "object" || Array.isArray(result)) {
return result;
}
const record = result as Record<string, unknown>;
if (record.structuredContent !== undefined) {
return record.structuredContent;
}
if (!Array.isArray(record.content)) {
return result;
}
const text = record.content
.map((part) => {
if (!part || typeof part !== "object" || Array.isArray(part)) {
return null;
}
const value = (part as Record<string, unknown>).text;
return typeof value === "string" ? value : null;
})
.filter((part): part is string => part !== null)
.join("\n");
return text || record.content;
}
@@ -1,21 +0,0 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
createPaseoToolRegistry,
type AgentMcpServerOptions,
type PaseoToolingRegistryOptions,
} from "./registry.js";
export async function createAgentMcpServer(options: AgentMcpServerOptions): Promise<McpServer> {
const server = new McpServer({
name: "agent-mcp",
version: "2.0.0",
});
const registry = createPaseoToolRegistry(options);
for (const tool of registry.listTools()) {
server.registerTool(tool.name, tool.config, tool.handler);
}
return server;
}
export type { AgentMcpServerOptions, PaseoToolingRegistryOptions };
@@ -1,24 +0,0 @@
import type { AgentLaunchContext, AgentSessionConfig } from "../agent-sdk-types.js";
const PASEO_MCP_SERVER_NAME = "paseo";
export function withPaseoToolingMcpServer(
config: AgentSessionConfig,
launchContext: AgentLaunchContext | undefined,
): AgentSessionConfig {
const mcpUrl = launchContext?.paseoTooling?.mcpUrl;
if (!mcpUrl || config.mcpServers?.[PASEO_MCP_SERVER_NAME]) {
return config;
}
return {
...config,
mcpServers: {
[PASEO_MCP_SERVER_NAME]: {
type: "http",
url: mcpUrl,
},
...config.mcpServers,
},
};
}
@@ -1,54 +0,0 @@
import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, test } from "vitest";
import { createTestLogger } from "../../../test-utils/test-logger.js";
import { createPaseoToolRegistry } from "./registry.js";
describe("Paseo tooling registry", () => {
test("executes tools with runtime caller identity", async () => {
const callerCwd = mkdtempSync(path.join(os.tmpdir(), "paseo-tooling-caller-"));
const requestedCwd = path.join(callerCwd, "child");
const createdTerminals: Array<{ cwd: string; name?: string }> = [];
const terminalManager = {
createTerminal: async (input: { cwd: string; name?: string }) => {
createdTerminals.push(input);
return { id: "terminal-1", name: input.name ?? "Terminal", cwd: input.cwd };
},
};
const agentManager = {
getAgent: (agentId: string) =>
agentId === "agent-1" ? { cwd: callerCwd, config: {} } : undefined,
};
try {
const registry = createPaseoToolRegistry({
agentManager: agentManager as never,
agentStorage: {} as never,
terminalManager: terminalManager as never,
providerSnapshotManager: {} as never,
agentScopedTools: true,
logger: createTestLogger(),
});
const result = await registry.executeTool({
name: "create_terminal",
callerAgentId: "agent-1",
input: {
cwd: "child",
name: "Build",
},
});
expect(createdTerminals).toEqual([{ cwd: requestedCwd, name: "Build" }]);
expect(result.structuredContent).toEqual({
id: "terminal-1",
name: "Build",
cwd: requestedCwd,
});
} finally {
rmSync(callerCwd, { recursive: true, force: true });
}
});
});
File diff suppressed because it is too large Load Diff
@@ -1,61 +0,0 @@
import { randomBytes } from "node:crypto";
import type {
AgentProvider,
PaseoToolingLaunchContext,
PaseoToolingProviderSessionRef,
} from "../agent-sdk-types.js";
interface PaseoToolingRuntimeEndpoints {
mcpBaseUrl: string | null;
httpBaseUrl: string | null;
}
export class PaseoToolingRuntime {
private mcpBaseUrl: string | null = null;
private httpBaseUrl: string | null = null;
private enabled = true;
private readonly sessionBindings = new Map<string, string>();
readonly token = randomBytes(32).toString("hex");
setEnabled(enabled: boolean): void {
this.enabled = enabled;
}
setEndpoints(endpoints: PaseoToolingRuntimeEndpoints): void {
this.mcpBaseUrl = endpoints.mcpBaseUrl;
this.httpBaseUrl = endpoints.httpBaseUrl;
}
createLaunchContext(agentId: string): PaseoToolingLaunchContext | undefined {
if (!this.enabled || (!this.mcpBaseUrl && !this.httpBaseUrl)) {
return undefined;
}
return {
agentId,
mcpUrl: this.mcpBaseUrl ? `${this.mcpBaseUrl}?callerAgentId=${agentId}` : null,
httpBaseUrl: this.httpBaseUrl,
token: this.token,
bindProviderSession: (ref) => this.bindProviderSession({ ...ref, agentId }),
};
}
bindProviderSession(ref: PaseoToolingProviderSessionRef & { agentId: string }): () => void {
const key = providerSessionKey(ref.provider, ref.sessionId);
this.sessionBindings.set(key, ref.agentId);
return () => {
if (this.sessionBindings.get(key) === ref.agentId) {
this.sessionBindings.delete(key);
}
};
}
resolveProviderSession(ref: PaseoToolingProviderSessionRef): string | null {
return this.sessionBindings.get(providerSessionKey(ref.provider, ref.sessionId)) ?? null;
}
}
function providerSessionKey(provider: AgentProvider, sessionId: string): string {
return `${provider}:${sessionId}`;
}
@@ -93,7 +93,6 @@ import {
type ProviderRuntimeSettings,
} from "../provider-launch-config.js";
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
import { withPaseoToolingMcpServer } from "../paseo-tooling/provider-mcp.js";
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
import { platformShell, spawnProcess } from "../../../utils/spawn.js";
@@ -597,30 +596,29 @@ export class ACPAgentClient implements AgentClient {
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
this.assertProvider(config);
const launchConfig = withPaseoToolingMcpServer(
const session = new ACPAgentSession(
{ ...config, provider: this.provider },
launchContext,
{
provider: this.provider,
logger: this.logger,
runtimeSettings: this.runtimeSettings,
defaultCommand: this.defaultCommand,
defaultModes: this.defaultModes,
modelTransformer: this.modelTransformer,
sessionResponseTransformer: this.sessionResponseTransformer,
configOptionsTransformer: this.configOptionsTransformer,
modeIdTransformer: this.modeIdTransformer,
toolSnapshotTransformer: this.toolSnapshotTransformer,
providerModeWriter: this.providerModeWriter,
beforeModeWriter: this.beforeModeWriter,
thinkingOptionWriter: this.thinkingOptionWriter,
capabilities: this.capabilities,
agentId: launchContext?.agentId,
launchEnv: launchContext?.env,
waitForInitialCommands: this.waitForInitialCommands,
initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
},
);
const session = new ACPAgentSession(launchConfig, {
provider: this.provider,
logger: this.logger,
runtimeSettings: this.runtimeSettings,
defaultCommand: this.defaultCommand,
defaultModes: this.defaultModes,
modelTransformer: this.modelTransformer,
sessionResponseTransformer: this.sessionResponseTransformer,
configOptionsTransformer: this.configOptionsTransformer,
modeIdTransformer: this.modeIdTransformer,
toolSnapshotTransformer: this.toolSnapshotTransformer,
providerModeWriter: this.providerModeWriter,
beforeModeWriter: this.beforeModeWriter,
thinkingOptionWriter: this.thinkingOptionWriter,
capabilities: this.capabilities,
agentId: launchContext?.agentId,
launchEnv: launchContext?.env,
waitForInitialCommands: this.waitForInitialCommands,
initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
});
await session.initializeNewSession();
return session;
}
@@ -640,15 +638,12 @@ export class ACPAgentClient implements AgentClient {
throw new Error(`${this.provider} resume requires the original working directory`);
}
const mergedConfig = withPaseoToolingMcpServer(
{
...storedConfig,
...overrides,
provider: this.provider,
cwd,
},
launchContext,
);
const mergedConfig: AgentSessionConfig = {
...storedConfig,
...overrides,
provider: this.provider,
cwd,
};
const session = new ACPAgentSession(mergedConfig, {
provider: this.provider,
logger: this.logger,
@@ -45,7 +45,6 @@ import { renderPromptAttachmentAsText } from "../../prompt-attachments.js";
import { claudeQuery, type ClaudeOptions, type ClaudeQueryFactory } from "./query.js";
import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from "./rewind.js";
import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
import { withPaseoToolingMcpServer } from "../../paseo-tooling/provider-mcp.js";
import { claudeProjectDirSync } from "./project-dir.js";
import {
@@ -1298,7 +1297,7 @@ export class ClaudeAgentClient implements AgentClient {
launchContext?: AgentLaunchContext,
options?: AgentCreateSessionOptions,
): Promise<AgentSession> {
const claudeConfig = this.assertConfig(withPaseoToolingMcpServer(config, launchContext));
const claudeConfig = this.assertConfig(config);
return new ClaudeAgentSession(claudeConfig, {
defaults: this.defaults,
runtimeSettings: this.runtimeSettings,
@@ -1326,7 +1325,7 @@ export class ClaudeAgentClient implements AgentClient {
provider: "claude",
cwd: merged.cwd,
};
const claudeConfig = this.assertConfig(withPaseoToolingMcpServer(mergedConfig, launchContext));
const claudeConfig = this.assertConfig(mergedConfig);
return new ClaudeAgentSession(claudeConfig, {
defaults: this.defaults,
runtimeSettings: this.runtimeSettings,
@@ -66,8 +66,8 @@ const cases: ParityCase[] = [
},
{
label: "non-existent path (canonicalize fallback)",
// realpath will throw; canonicalize falls back to NFC of input. SDK does
// the same, so paths should still match.
// realpath will throw; canonicalize falls back to the input path. SDK does
// the same, including platform-specific normalization, so paths should match.
build: async () => join(workspaceRoot, "this-path-was-never-created-" + randomUUID()),
},
];
@@ -6,8 +6,8 @@ import { join } from "node:path";
// Verbatim port of the Claude Agent SDK's project-directory encoding so
// paseo computes the same `~/.claude/projects/<dir>` path the SDK does.
// The SDK ships only as a precompiled bundle; grep the JS source at
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs for `function f1`,
// `function i4`, `function tB`, `S0=200`.
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs for `function Ar`,
// `function wo`, `function kn`, `function Lq`, `Ai=200`.
const PROJECT_DIR_LENGTH_CAP = 200;
@@ -32,20 +32,24 @@ export function claudeProjectDirSync(cwd: string, options?: ClaudeProjectDirOpti
async function canonicalize(input: string): Promise<string> {
try {
return (await realpath(input)).normalize("NFC");
return normalizeLikeSdk(await realpath(input));
} catch {
return input.normalize("NFC");
return normalizeLikeSdk(input);
}
}
function canonicalizeSync(input: string): string {
try {
return realpathSync.native(input).normalize("NFC");
return normalizeLikeSdk(realpathSync.native(input));
} catch {
return input.normalize("NFC");
return normalizeLikeSdk(input);
}
}
function normalizeLikeSdk(path: string): string {
return process.platform === "darwin" ? path.normalize("NFC") : path;
}
function encode(input: string): string {
const replaced = input.replace(/[^a-zA-Z0-9]/g, "-");
if (replaced.length <= PROJECT_DIR_LENGTH_CAP) {
@@ -85,7 +85,6 @@ import {
toDiagnosticErrorMessage,
} from "./diagnostic-utils.js";
import { runProviderTurn } from "./provider-runner.js";
import { withPaseoToolingMcpServer } from "../paseo-tooling/provider-mcp.js";
import type { WorkspaceGitService } from "../../workspace-git-service.js";
function assertChildWithPipes(
@@ -5434,10 +5433,7 @@ export class CodexAppServerAgentClient implements AgentClient {
// TODO: Honor persistSession=false if app-server adds support, or route
// utility generations through `codex exec --ephemeral` in a larger change.
}
const sessionConfig = withPaseoToolingMcpServer(
{ ...config, provider: CODEX_PROVIDER },
launchContext,
);
const sessionConfig: AgentSessionConfig = { ...config, provider: CODEX_PROVIDER };
const goalsEnabled = await this.resolveGoalsEnabled();
const autoReviewEnabled = await this.resolveAutoReviewEnabled();
const session = new CodexAppServerAgentSession(
@@ -5462,15 +5458,12 @@ export class CodexAppServerAgentClient implements AgentClient {
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
const storedConfig = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
const merged = withPaseoToolingMcpServer(
{
...storedConfig,
...overrides,
provider: CODEX_PROVIDER,
cwd: overrides?.cwd ?? storedConfig.cwd ?? process.cwd(),
},
launchContext,
);
const merged: AgentSessionConfig = {
...storedConfig,
...overrides,
provider: CODEX_PROVIDER,
cwd: overrides?.cwd ?? storedConfig.cwd ?? process.cwd(),
};
const goalsEnabled = await this.resolveGoalsEnabled();
const autoReviewEnabled = await this.resolveAutoReviewEnabled();
const session = new CodexAppServerAgentSession(
@@ -16,7 +16,6 @@ import {
TestOpenCodeRuntime,
} from "./opencode/test-utils/test-opencode-runtime.js";
import type {
AgentLaunchContext,
AgentSessionConfig,
AgentStreamEvent,
ToolCallTimelineItem,
@@ -853,55 +852,6 @@ describe("OpenCode adapter context-window normalization", () => {
});
describe("OpenCode adapter startTurn error handling", () => {
test("uses native Paseo tooling plugin instead of registering internal MCP", async () => {
const runtime = new TestOpenCodeRuntime();
const openCodeClient = new TestOpenCodeClient();
runtime.enqueueClient(openCodeClient);
const cwd = tmpCwd();
const client = new OpenCodeAgentClient(createTestLogger(), undefined, { runtime });
const boundSessions: string[] = [];
const unboundSessions: string[] = [];
const launchContext: AgentLaunchContext = {
agentId: "agent-1",
env: { PASEO_AGENT_ID: "agent-1" },
paseoTooling: {
agentId: "agent-1",
mcpUrl: "http://127.0.0.1:6767/mcp/agents?callerAgentId=agent-1",
httpBaseUrl: "http://127.0.0.1:6767",
token: "tool-token",
bindProviderSession: ({ provider, sessionId }) => {
expect(provider).toBe("opencode");
boundSessions.push(sessionId);
return () => unboundSessions.push(sessionId);
},
},
};
try {
const session = await client.createSession(
{
provider: "opencode",
cwd,
},
launchContext,
);
await collectTurnEvents(streamSession(session, "hello"));
expect(runtime.acquisitions[0]?.env?.PASEO_AGENT_ID).toBe("agent-1");
expect(runtime.acquisitions[0]?.env?.OPENCODE_CONFIG_CONTENT).toContain(
"paseo-tooling-plugin.mjs",
);
expect(openCodeClient.calls.mcpAdd).toEqual([]);
expect(boundSessions).toEqual(["session-1"]);
await session.close();
expect(unboundSessions).toEqual(["session-1"]);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
});
test("dynamically adds injected MCP servers without config-backed connect", async () => {
const runtime = new TestOpenCodeRuntime();
const openCodeClient = new TestOpenCodeClient();
@@ -78,11 +78,6 @@ import {
type OpenCodeRuntime,
type OpenCodeServerAcquisition,
} from "./opencode/runtime.js";
import {
bindOpenCodePaseoToolingSession,
ensureOpenCodePaseoToolingRuntime,
withOpenCodePaseoToolingEnv,
} from "./opencode/paseo-tooling.js";
import { normalizeProviderReplayTimestamp } from "../provider-history-timestamps.js";
import { revertOpenCodeConversationAndFiles } from "./opencode/rewind.js";
@@ -1285,14 +1280,9 @@ export class OpenCodeAgentClient implements AgentClient {
options?: AgentCreateSessionOptions,
): Promise<AgentSession> {
const openCodeConfig = this.assertConfig(config);
const paseoToolingRuntime = ensureOpenCodePaseoToolingRuntime();
const acquisition = await this.runtime.acquireServer({
force: false,
env: withOpenCodePaseoToolingEnv({
env: launchContext?.env,
launchContext,
runtime: paseoToolingRuntime,
}),
env: launchContext?.env,
});
const { url } = acquisition.server;
const client = this.runtime.createClient({
@@ -1317,10 +1307,6 @@ export class OpenCodeAgentClient implements AgentClient {
}
await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
const unbindPaseoToolingSession = bindOpenCodePaseoToolingSession({
launchContext,
sessionId: session.id,
});
return new OpenCodeAgentSession(
openCodeConfig,
@@ -1331,7 +1317,6 @@ export class OpenCodeAgentClient implements AgentClient {
acquisition.release,
options?.persistSession,
launchContext?.agentId,
unbindPaseoToolingSession,
);
} catch (error) {
acquisition.release();
@@ -1357,15 +1342,7 @@ export class OpenCodeAgentClient implements AgentClient {
cwd,
};
const openCodeConfig = this.assertConfig(config);
const paseoToolingRuntime = ensureOpenCodePaseoToolingRuntime();
const acquisition = await this.runtime.acquireServer({
force: false,
env: withOpenCodePaseoToolingEnv({
env: launchContext?.env,
launchContext,
runtime: paseoToolingRuntime,
}),
});
const acquisition = await this.runtime.acquireServer({ force: false });
const { url } = acquisition.server;
const client = this.runtime.createClient({
baseUrl: url,
@@ -1374,10 +1351,6 @@ export class OpenCodeAgentClient implements AgentClient {
try {
await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
const unbindPaseoToolingSession = bindOpenCodePaseoToolingSession({
launchContext,
sessionId: handle.sessionId,
});
return new OpenCodeAgentSession(
openCodeConfig,
@@ -1388,7 +1361,6 @@ export class OpenCodeAgentClient implements AgentClient {
acquisition.release,
undefined,
launchContext?.agentId,
unbindPaseoToolingSession,
);
} catch (error) {
acquisition.release();
@@ -2830,7 +2802,6 @@ class OpenCodeAgentSession implements AgentSession {
releaseServer?: () => void,
persistSession = true,
private readonly agentId?: string,
private readonly unbindPaseoToolingSession?: () => void,
) {
this.config = config;
this.client = client;
@@ -3603,7 +3574,6 @@ class OpenCodeAgentSession implements AgentSession {
await this.deleteProviderSessionIfEphemeral();
this.activeForegroundTurnId = null;
} finally {
this.unbindPaseoToolingSession?.();
this.releaseServer?.();
this.releaseServer = null;
}
@@ -1,103 +0,0 @@
import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, test, vi } from "vitest";
import { ensureOpenCodePaseoToolingRuntime } from "./paseo-tooling.js";
describe("OpenCode Paseo tooling plugin", () => {
const originalPaseoHome = process.env.PASEO_HOME;
afterEach(() => {
if (originalPaseoHome === undefined) {
delete process.env.PASEO_HOME;
} else {
process.env.PASEO_HOME = originalPaseoHome;
}
vi.restoreAllMocks();
});
test("normalizes OpenCode string arguments before executing Paseo tools", async () => {
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "opencode-paseo-tooling-test-"));
process.env.PASEO_HOME = paseoHome;
const runtime = ensureOpenCodePaseoToolingRuntime();
const postedBodies: unknown[] = [];
const fetchMock = vi.fn(async (url: string | URL, init?: RequestInit) => {
const pathname = new URL(url.toString()).pathname;
if (pathname === "/api/paseo-tooling/manifest") {
return new Response(
JSON.stringify({
tools: [
{
name: "list_agents",
description: "List agents",
inputJsonSchema: {
type: "object",
properties: {
includeArchived: { type: "boolean", default: false },
sinceHours: { type: "integer", default: 48 },
statuses: {
type: "array",
items: { type: "string", enum: ["running", "idle"] },
},
limit: { type: "integer", default: 50 },
},
additionalProperties: false,
},
},
],
}),
{ status: 200 },
);
}
postedBodies.push(JSON.parse(String(init?.body)) as unknown);
return new Response(JSON.stringify({ output: "ok" }), { status: 200 });
});
vi.stubGlobal("fetch", fetchMock);
try {
const plugin = (await import(
`${pathToFileURL(runtime.pluginPath).href}?test=${Date.now()}`
)) as {
server: (
input: unknown,
options: { baseUrl: string; token: string },
) => Promise<{
tool: Record<
string,
{ execute: (args: unknown, context: { sessionID: string }) => Promise<string> }
>;
}>;
};
const server = await plugin.server({}, { baseUrl: "http://127.0.0.1:6767", token: "t" });
await server.tool.paseo_list_agents?.execute(
{
includeArchived: "False",
sinceHours: "",
statuses: '["running"]',
limit: "5",
},
{ sessionID: "session-1" },
);
expect(postedBodies).toEqual([
{
provider: "opencode",
sessionId: "session-1",
tool: "list_agents",
input: {
includeArchived: false,
statuses: ["running"],
limit: 5,
},
},
]);
} finally {
rmSync(paseoHome, { recursive: true, force: true });
}
});
});
@@ -1,276 +0,0 @@
import { createRequire } from "node:module";
import { mkdirSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import type { AgentLaunchContext } from "../../agent-sdk-types.js";
const PASEO_OPENCODE_PLUGIN_FILENAME = "paseo-tooling-plugin.mjs";
export interface OpenCodePaseoToolingRuntime {
pluginPath: string;
}
function resolvePaseoHome(): string {
return process.env.PASEO_HOME?.trim() || path.join(homedir(), ".paseo");
}
function resolveRuntimeDirectory(): string {
return path.join(resolvePaseoHome(), "opencode");
}
export function ensureOpenCodePaseoToolingRuntime(): OpenCodePaseoToolingRuntime {
const dir = resolveRuntimeDirectory();
mkdirSync(dir, { recursive: true });
const pluginPath = path.join(dir, PASEO_OPENCODE_PLUGIN_FILENAME);
const require = createRequire(import.meta.url);
const zodImportUrl = pathToFileURL(require.resolve("zod")).href;
writeFileSync(pluginPath, createOpenCodePaseoToolingPluginSource(zodImportUrl), "utf8");
return { pluginPath };
}
export function withOpenCodePaseoToolingEnv(params: {
env?: Record<string, string>;
launchContext?: AgentLaunchContext;
runtime: OpenCodePaseoToolingRuntime;
}): Record<string, string> | undefined {
const tooling = params.launchContext?.paseoTooling;
if (!tooling?.httpBaseUrl) {
return params.env;
}
const pluginSpec = [
pathToFileURL(params.runtime.pluginPath).href,
{
baseUrl: tooling.httpBaseUrl,
token: tooling.token,
},
];
return {
...params.env,
OPENCODE_CONFIG_CONTENT: mergeOpenCodeConfigContent(
params.env?.OPENCODE_CONFIG_CONTENT,
pluginSpec,
),
};
}
export function bindOpenCodePaseoToolingSession(params: {
launchContext?: AgentLaunchContext;
sessionId: string;
}): (() => void) | undefined {
return params.launchContext?.paseoTooling?.bindProviderSession({
provider: "opencode",
sessionId: params.sessionId,
});
}
function mergeOpenCodeConfigContent(existing: string | undefined, pluginSpec: unknown[]): string {
const base = parseConfigContent(existing);
const existingPlugins = Array.isArray(base.plugin) ? base.plugin : [];
const pluginUrl = pluginSpec[0];
const withoutExistingPaseoPlugin = existingPlugins.filter((entry) => {
const spec = Array.isArray(entry) ? entry[0] : entry;
return spec !== pluginUrl;
});
return JSON.stringify({
...base,
plugin: [...withoutExistingPaseoPlugin, pluginSpec],
});
}
function parseConfigContent(existing: string | undefined): Record<string, unknown> {
if (!existing?.trim()) {
return {};
}
try {
const parsed = JSON.parse(existing) as unknown;
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: {};
} catch {
return {};
}
}
function createOpenCodePaseoToolingPluginSource(zodImportUrl: string): string {
return `
import { z } from ${JSON.stringify(zodImportUrl)};
export async function server(_input, options) {
const manifestResponse = await fetch(new URL("/api/paseo-tooling/manifest", options.baseUrl), {
headers: { authorization: "Bearer " + options.token },
});
if (!manifestResponse.ok) {
throw new Error("Failed to load Paseo tooling manifest: " + manifestResponse.status);
}
const manifest = await manifestResponse.json();
const tools = {};
for (const item of manifest.tools ?? []) {
const id = "paseo_" + item.name;
tools[id] = {
description: item.description,
args: jsonSchemaObjectToZodShape(item.inputJsonSchema),
execute: async (args, context) => {
const input = normalizeJsonSchemaInput(args, item.inputJsonSchema);
const response = await fetch(new URL("/api/paseo-tooling/execute", options.baseUrl), {
method: "POST",
headers: {
authorization: "Bearer " + options.token,
"content-type": "application/json",
},
body: JSON.stringify({
provider: "opencode",
sessionId: context.sessionID,
tool: item.name,
input,
}),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(payload.error ?? "Paseo tool failed: " + response.status);
}
return typeof payload.output === "string" ? payload.output : JSON.stringify(payload.output ?? payload, null, 2);
},
};
}
return { tool: tools };
}
function jsonSchemaObjectToZodShape(schema) {
const properties = schema?.properties && typeof schema.properties === "object" ? schema.properties : {};
const required = new Set(Array.isArray(schema?.required) ? schema.required : []);
const shape = {};
for (const [key, value] of Object.entries(properties)) {
let parsed = jsonSchemaToZod(value);
if (!required.has(key)) parsed = parsed.optional();
shape[key] = parsed;
}
return shape;
}
function jsonSchemaToZod(schema) {
if (!schema || typeof schema !== "object") return z.any();
if (schema.anyOf) return unionToZod(schema.anyOf);
if (schema.oneOf) return unionToZod(schema.oneOf);
if (schema.const !== undefined) return z.literal(schema.const);
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
if (schema.enum.length === 1) return z.literal(schema.enum[0]);
if (schema.enum.every((item) => typeof item === "string")) return z.enum(schema.enum);
return z.union(schema.enum.map((item) => z.literal(item)));
}
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
const nullable = types.includes("null");
const type = types.find((item) => item !== "null");
let result;
switch (type) {
case "string":
result = z.string();
if (typeof schema.minLength === "number") result = result.min(schema.minLength);
if (typeof schema.maxLength === "number") result = result.max(schema.maxLength);
break;
case "integer":
result = z.number().int();
if (typeof schema.minimum === "number") result = result.min(schema.minimum);
if (typeof schema.maximum === "number") result = result.max(schema.maximum);
break;
case "number":
result = z.number();
if (typeof schema.minimum === "number") result = result.min(schema.minimum);
if (typeof schema.maximum === "number") result = result.max(schema.maximum);
break;
case "boolean":
result = z.boolean();
break;
case "array":
result = z.array(jsonSchemaToZod(schema.items));
break;
case "object":
result = z.object(jsonSchemaObjectToZodShape(schema));
break;
default:
result = z.any();
break;
}
if (schema.description && typeof result.describe === "function") result = result.describe(schema.description);
return nullable ? result.nullable() : result;
}
function normalizeJsonSchemaInput(input, schema) {
if (!schema || typeof schema !== "object") return input;
if (schema.anyOf) return normalizeUnionInput(input, schema.anyOf);
if (schema.oneOf) return normalizeUnionInput(input, schema.oneOf);
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
const type = types.find((item) => item !== "null");
if (typeof input === "string" && !types.includes("string")) {
const trimmed = input.trim();
if (!trimmed || trimmed === "undefined" || trimmed === "null") return undefined;
}
switch (type) {
case "boolean":
if (typeof input === "string") {
const normalized = input.trim().toLowerCase();
if (normalized === "true" || normalized === "yes" || normalized === "1") return true;
if (normalized === "false" || normalized === "no" || normalized === "0") return false;
}
return input;
case "integer":
case "number":
if (typeof input === "string" && input.trim() !== "" && /^-?\\d+(\\.\\d+)?$/.test(input.trim())) {
return Number(input);
}
return input;
case "array": {
const value = parseJsonString(input);
if (!Array.isArray(value)) return value;
return value.map((item) => normalizeJsonSchemaInput(item, schema.items)).filter((item) => item !== undefined);
}
case "object": {
const value = parseJsonString(input);
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
const normalized = {};
for (const [key, propertySchema] of Object.entries(properties)) {
if (!(key in value)) continue;
const next = normalizeJsonSchemaInput(value[key], propertySchema);
if (next !== undefined || required.has(key)) normalized[key] = next;
}
return normalized;
}
default:
return input;
}
}
function normalizeUnionInput(input, schemas) {
const preferred = schemas.find((item) => item?.type && item.type !== "null") ?? schemas[0];
return normalizeJsonSchemaInput(input, preferred);
}
function parseJsonString(value) {
if (typeof value !== "string") return value;
const trimmed = value.trim();
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) return value;
try {
return JSON.parse(trimmed);
} catch {
return value;
}
}
function unionToZod(items) {
const nonNull = items.filter((item) => item?.type !== "null");
const nullable = nonNull.length !== items.length;
const parsed = nonNull.map(jsonSchemaToZod);
const result = parsed.length === 0 ? z.null() : parsed.length === 1 ? parsed[0] : z.union(parsed);
return nullable ? result.nullable() : result;
}
`;
}
@@ -1,4 +1,4 @@
import type { Event as OpenCodeEvent, OpencodeClient } from "@opencode-ai/sdk/v2/client";
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
import type { OpenCodeRuntime, OpenCodeServerAcquisition } from "../runtime.js";
@@ -92,7 +92,7 @@ export class TestOpenCodeClient {
sessionMessagesResponse: OpenCodeResponse = { data: [] };
sessionPromptAsyncEvents: unknown[] = [idleEvent()];
sessionPromptAsyncResponse: OpenCodeResponse = {};
sessionSummarizeEvents: OpenCodeEvent[] = [];
sessionSummarizeEvents: unknown[] = [idleEvent()];
sessionSummarizeResponse: OpenCodeResponse = { data: {} };
sessionUpdateResponse: OpenCodeResponse = {};
private readonly queuedEventStream = createQueuedEventStream();
@@ -30,7 +30,6 @@ import {
type PersistedAgentDescriptor,
} from "../../agent-sdk-types.js";
import { runProviderTurn } from "../provider-runner.js";
import { withPaseoToolingMcpServer } from "../../paseo-tooling/provider-mcp.js";
import {
checkProviderLaunchAvailable,
resolveProviderLaunch,
@@ -1839,19 +1838,18 @@ export class PiRpcAgentClient implements AgentClient {
config: AgentSessionConfig,
launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
const launchConfig = withPaseoToolingMcpServer(config, launchContext);
const mcpConfig = await this.prepareMcpConfig(launchConfig.cwd, launchConfig.mcpServers);
const mcpConfig = await this.prepareMcpConfig(config.cwd, config.mcpServers);
const paseoExtension = createPiPaseoExtensionFile();
let runtimeSession: PiRuntimeSession;
try {
runtimeSession = await this.runtime.startSession({
cwd: launchConfig.cwd,
model: launchConfig.model,
cwd: config.cwd,
model: config.model,
thinkingOptionId:
normalizePiThinkingOption(launchConfig.thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL,
normalizePiThinkingOption(config.thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL,
systemPrompt: composeSystemPromptParts(
launchConfig.systemPrompt,
launchConfig.daemonAppendSystemPrompt,
config.systemPrompt,
config.daemonAppendSystemPrompt,
),
env: launchContext?.env,
mcpConfigPath: mcpConfig?.path,
@@ -1865,7 +1863,7 @@ export class PiRpcAgentClient implements AgentClient {
try {
return new PiRpcAgentSession({
runtimeSession,
config: launchConfig,
config,
initialState: await runtimeSession.getState(),
capabilities: withPiMcpCapability(mcpConfig !== null),
cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
@@ -1881,7 +1879,7 @@ export class PiRpcAgentClient implements AgentClient {
async resumeSession(
handle: AgentPersistenceHandle,
overrides?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
_launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
const sessionFile = handle.nativeHandle;
if (!sessionFile) {
@@ -1891,8 +1889,7 @@ export class PiRpcAgentClient implements AgentClient {
const persistenceMetadata = parsePersistenceMetadata(handle.metadata);
const resumeConfig = buildResumeConfig(persistenceMetadata, overrides);
const launchConfig = withPaseoToolingMcpServer(resumeConfig.config, launchContext);
const mcpConfig = await this.prepareMcpConfig(resumeConfig.cwd, launchConfig.mcpServers);
const mcpConfig = await this.prepareMcpConfig(resumeConfig.cwd, resumeConfig.config.mcpServers);
const paseoExtension = createPiPaseoExtensionFile();
let runtimeSession: PiRuntimeSession;
try {
@@ -1902,8 +1899,8 @@ export class PiRpcAgentClient implements AgentClient {
model: resumeConfig.model,
thinkingOptionId: normalizePiThinkingOption(resumeConfig.thinkingOptionId) ?? undefined,
systemPrompt: composeSystemPromptParts(
launchConfig.systemPrompt,
launchConfig.daemonAppendSystemPrompt,
resumeConfig.config.systemPrompt,
resumeConfig.config.daemonAppendSystemPrompt,
),
mcpConfigPath: mcpConfig?.path,
extensionPaths: [paseoExtension.path],
@@ -1916,7 +1913,7 @@ export class PiRpcAgentClient implements AgentClient {
try {
return new PiRpcAgentSession({
runtimeSession,
config: launchConfig,
config: resumeConfig.config,
initialState: await runtimeSession.getState(),
capabilities: withPiMcpCapability(mcpConfig !== null),
cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
@@ -424,11 +424,12 @@ interface ToolReadOutputValue {
content?: string;
}
export const ToolReadOutputSchema: z.ZodType<ToolReadOutputValue, z.ZodTypeDef, unknown> =
ToolReadOutputContentSchema;
export const ToolReadOutputSchema: z.ZodType<ToolReadOutputValue> = ToolReadOutputContentSchema;
export const ToolReadOutputWithPathSchema: z.ZodType<ToolReadOutputValue, z.ZodTypeDef, unknown> =
z.union([ToolReadOutputContentSchema, ToolReadOutputPathSchema]);
export const ToolReadOutputWithPathSchema: z.ZodType<ToolReadOutputValue> = z.union([
ToolReadOutputContentSchema,
ToolReadOutputPathSchema,
]);
export const ToolWriteContentSchema = z
.object({
@@ -934,8 +935,12 @@ export function toolDetailBranchByName<
input: inputSchema.nullable(),
output: outputSchema.nullable(),
});
return schema.transform((value: z.infer<typeof schema>) => {
return mapper(value.input, value.output);
return schema.transform((value) => {
const parsed = value as unknown as {
input: z.infer<InputSchema> | null;
output: z.infer<OutputSchema> | null;
};
return mapper(parsed.input, parsed.output);
});
}
@@ -957,8 +962,12 @@ export function toolDetailBranchByToolName<
input: inputSchema.nullable(),
output: outputSchema.nullable(),
});
return schema.transform((value: z.infer<typeof schema>) => {
return mapper(value.input, value.output);
return schema.transform((value) => {
const parsed = value as unknown as {
input: z.infer<InputSchema> | null;
output: z.infer<OutputSchema> | null;
};
return mapper(parsed.input, parsed.output);
});
}
@@ -982,7 +991,12 @@ export function toolDetailBranchByNameWithCwd<
output: outputSchema.nullable(),
cwd: z.string().optional().nullable(),
});
return schema.transform((value: z.infer<typeof schema>) => {
return mapper(value.input, value.output, value.cwd ?? null);
return schema.transform((value) => {
const parsed = value as unknown as {
input: z.infer<InputSchema> | null;
output: z.infer<OutputSchema> | null;
cwd?: string | null;
};
return mapper(parsed.input, parsed.output, parsed.cwd ?? null);
});
}
@@ -26,6 +26,28 @@ export function stripInternalPaseoMcpServer(config: AgentSessionConfig): AgentSe
return next;
}
export function withRuntimePaseoMcpServer(params: {
config: AgentSessionConfig;
agentId: string;
mcpBaseUrl: string | null;
}): AgentSessionConfig {
const storedConfig = stripInternalPaseoMcpServer(params.config);
if (!params.mcpBaseUrl || storedConfig.mcpServers?.[PASEO_MCP_SERVER_NAME]) {
return storedConfig;
}
return {
...storedConfig,
mcpServers: {
[PASEO_MCP_SERVER_NAME]: {
type: "http",
url: `${params.mcpBaseUrl}?callerAgentId=${params.agentId}`,
},
...storedConfig.mcpServers,
},
};
}
function isInternalPaseoMcpServer(config: McpServerConfig): boolean {
if (config.type !== "http" && config.type !== "sse") {
return false;
+74 -113
View File
@@ -99,10 +99,6 @@ import { AgentManager } from "./agent/agent-manager.js";
import { AgentStorage } from "./agent/agent-storage.js";
import { attachAgentStoragePersistence } from "./persistence-hooks.js";
import { createAgentMcpServer } from "./agent/mcp-server.js";
import { createPaseoToolRegistry } from "./agent/paseo-tooling/registry.js";
import { mountPaseoToolingHttpAdapter } from "./agent/paseo-tooling/http-adapter.js";
import { PaseoToolingRuntime } from "./agent/paseo-tooling/runtime.js";
import type { PaseoToolingRegistryOptions } from "./agent/paseo-tooling/registry.js";
import { ProviderSnapshotManager } from "./agent/provider-snapshot-manager.js";
import { bootstrapWorkspaceRegistries } from "./workspace-registry-bootstrap.js";
import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
@@ -169,14 +165,6 @@ function createAgentMcpBaseUrl(listenTarget: ListenTarget | null): string | null
).toString();
}
function createDaemonHttpBaseUrl(listenTarget: ListenTarget | null): string | null {
if (!listenTarget || listenTarget.type !== "tcp") {
return null;
}
const host = resolveAgentMcpClientHost(listenTarget.host);
return `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`;
}
function summarizeAgentMcpDebugMessage(body: unknown): Record<string, unknown> {
if (!body || typeof body !== "object" || Array.isArray(body)) {
return {
@@ -554,13 +542,10 @@ export async function createPaseoDaemon(
extraClients: config.agentClients,
});
const initialAgentManagerState = providerSnapshotManager.getAgentManagerProviderState();
const paseoToolingRuntime = new PaseoToolingRuntime();
paseoToolingRuntime.setEnabled(config.mcpInjectIntoAgents !== false);
const agentManager = new AgentManager({
clients: initialAgentManagerState.clients,
providerDefinitions: initialAgentManagerState.providerDefinitions,
registry: agentStorage,
paseoToolingRuntime,
appendSystemPrompt: config.appendSystemPrompt,
logger,
});
@@ -704,85 +689,79 @@ export async function createPaseoDaemon(
const agentMcpRoute = "/mcp/agents";
const agentMcpTransports: AgentMcpTransportMap = new Map();
const buildPaseoToolingRegistryOptions = (
callerAgentId?: string,
): PaseoToolingRegistryOptions => ({
agentManager,
agentStorage,
terminalManager,
getDaemonTcpPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
scheduleService,
providerSnapshotManager,
github,
workspaceGitService,
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
markWorkspaceArchiving: markWorkspaceArchivingExternal,
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
createPaseoWorktree: async (input, serviceOptions) => {
return createPaseoWorktreeWorkflow(
{
paseoHome: config.paseoHome,
worktreesRoot: config.worktreesRoot,
createPaseoWorktree: async (workflowInput, workflowOptions) => {
return createRegisteredPaseoWorktree(workflowInput, {
github,
...(workflowOptions?.resolveDefaultBranch
? {
resolveDefaultBranch: workflowOptions.resolveDefaultBranch,
}
: {}),
projectRegistry,
workspaceRegistry,
workspaceGitService,
});
},
warmWorkspaceGitData: async (workspace) => {
await Promise.all(
wsServer
?.listActiveSessions()
.map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? [],
);
},
emitWorkspaceUpdateForCwd: async (cwd, emitOptions) => {
await Promise.all(
wsServer
?.listActiveSessions()
.map((session) => session.emitWorkspaceUpdatesForExternalCwds([cwd])) ?? [],
);
void emitOptions;
},
cacheWorkspaceSetupSnapshot: () => {},
emit: emitExternalSessionMessage,
sessionLogger: logger,
terminalManager,
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
serviceProxy,
scriptRuntimeStore,
getDaemonTcpPort: () =>
boundListenTarget?.type === "tcp" ? boundListenTarget.port : null,
getDaemonTcpHost: () =>
boundListenTarget?.type === "tcp" ? boundListenTarget.host : null,
serviceProxyPublicBaseUrl,
onScriptsChanged: null,
},
input,
serviceOptions,
);
},
paseoHome: config.paseoHome,
worktreesRoot: config.worktreesRoot,
callerAgentId,
enableVoiceTools: false,
resolveSpeakHandler: (agentId) => wsServer?.resolveVoiceSpeakHandler(agentId) ?? null,
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
logger,
});
const createAgentMcpTransport = async (callerAgentId?: string) => {
const agentMcpServer = await createAgentMcpServer(
buildPaseoToolingRegistryOptions(callerAgentId),
);
const agentMcpServer = await createAgentMcpServer({
agentManager,
agentStorage,
terminalManager,
getDaemonTcpPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
scheduleService,
providerSnapshotManager,
github,
workspaceGitService,
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
markWorkspaceArchiving: markWorkspaceArchivingExternal,
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
createPaseoWorktree: async (input, serviceOptions) => {
return createPaseoWorktreeWorkflow(
{
paseoHome: config.paseoHome,
worktreesRoot: config.worktreesRoot,
createPaseoWorktree: async (workflowInput, workflowOptions) => {
return createRegisteredPaseoWorktree(workflowInput, {
github,
...(workflowOptions?.resolveDefaultBranch
? {
resolveDefaultBranch: workflowOptions.resolveDefaultBranch,
}
: {}),
projectRegistry,
workspaceRegistry,
workspaceGitService,
});
},
warmWorkspaceGitData: async (workspace) => {
await Promise.all(
wsServer
?.listActiveSessions()
.map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? [],
);
},
emitWorkspaceUpdateForCwd: async (cwd, emitOptions) => {
await Promise.all(
wsServer
?.listActiveSessions()
.map((session) => session.emitWorkspaceUpdatesForExternalCwds([cwd])) ?? [],
);
void emitOptions;
},
cacheWorkspaceSetupSnapshot: () => {},
emit: emitExternalSessionMessage,
sessionLogger: logger,
terminalManager,
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
serviceProxy,
scriptRuntimeStore,
getDaemonTcpPort: () =>
boundListenTarget?.type === "tcp" ? boundListenTarget.port : null,
getDaemonTcpHost: () =>
boundListenTarget?.type === "tcp" ? boundListenTarget.host : null,
serviceProxyPublicBaseUrl,
onScriptsChanged: null,
},
input,
serviceOptions,
);
},
paseoHome: config.paseoHome,
worktreesRoot: config.worktreesRoot,
callerAgentId,
enableVoiceTools: false,
resolveSpeakHandler: (agentId) => wsServer?.resolveVoiceSpeakHandler(agentId) ?? null,
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
logger,
});
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
@@ -891,16 +870,6 @@ export async function createPaseoDaemon(
void runAgentMcpRequest(req, res);
};
mountPaseoToolingHttpAdapter({
app,
registry: createPaseoToolRegistry({
...buildPaseoToolingRegistryOptions(),
agentScopedTools: true,
}),
runtime: paseoToolingRuntime,
logger,
});
app.post(agentMcpRoute, handleAgentMcpRequest);
app.get(agentMcpRoute, handleAgentMcpRequest);
app.delete(agentMcpRoute, handleAgentMcpRequest);
@@ -948,18 +917,10 @@ export async function createPaseoDaemon(
const logAndResolve = async () => {
boundListenTarget = resolveBoundListenTarget(listenTarget, httpServer);
const mcpBaseUrl = mcpEnabled ? createAgentMcpBaseUrl(boundListenTarget) : null;
const httpBaseUrl = createDaemonHttpBaseUrl(boundListenTarget);
agentMcpBaseUrl = config.mcpInjectIntoAgents === false ? null : mcpBaseUrl;
paseoToolingRuntime.setEndpoints({
mcpBaseUrl: agentMcpBaseUrl,
httpBaseUrl: config.mcpInjectIntoAgents === false ? null : httpBaseUrl,
});
agentManager.setMcpBaseUrl(agentMcpBaseUrl);
daemonConfigStore.onFieldChange("mcp.injectIntoAgents", (value) => {
paseoToolingRuntime.setEnabled(value !== false);
paseoToolingRuntime.setEndpoints({
mcpBaseUrl: value ? mcpBaseUrl : null,
httpBaseUrl: value ? httpBaseUrl : null,
});
agentManager.setMcpBaseUrl(value ? mcpBaseUrl : null);
});
daemonConfigStore.onFieldChange("appendSystemPrompt", (value) => {
agentManager.setAppendSystemPrompt(typeof value === "string" ? value : "");
+18 -8
View File
@@ -173,13 +173,17 @@ async function main() {
const supervisorPid = process.ppid;
let lastSupervisorHeartbeatAt = Date.now();
let supervisorShutdownRequested = false;
const requestSupervisorShutdown = (reason: string) => {
if (supervisorShutdownRequested) {
let supervisorExitRequested = false;
const exitAfterSupervisorLoss = () => {
if (supervisorExitRequested) {
return;
}
supervisorShutdownRequested = true;
beginShutdown(reason);
supervisorExitRequested = true;
// The supervisor owns the worker's stdout/stderr pipes. Once it is gone,
// logging during graceful shutdown can block on the broken pipe and leave
// the daemon orphaned, so supervisor loss is a hard process boundary.
process.exit(0);
};
process.on("message", (message: unknown) => {
@@ -192,13 +196,19 @@ async function main() {
lastSupervisorHeartbeatAt = Date.now();
}
});
process.on("disconnect", () => requestSupervisorShutdown("supervisor disconnect"));
process.on("disconnect", exitAfterSupervisorLoss);
const timer = setInterval(() => {
const ipcConnected = typeof process.connected === "boolean" ? process.connected : true;
const heartbeatExpired = Date.now() - lastSupervisorHeartbeatAt > 3500;
if (ipcConnected === false || !isPidAlive(supervisorPid) || heartbeatExpired) {
requestSupervisorShutdown("supervisor disconnect");
const supervisorChanged = process.ppid !== supervisorPid;
if (
ipcConnected === false ||
supervisorChanged ||
!isPidAlive(supervisorPid) ||
heartbeatExpired
) {
exitAfterSupervisorLoss();
}
}, 1000);
timer.unref();
@@ -45,9 +45,13 @@ export interface LocalSpeechSttLanguageConfig {
const NumberLikeSchema = z.union([z.number(), z.string().trim().min(1)]);
const LanguageSchema = z.string().trim().min(1).default(DEFAULT_STT_LANGUAGE);
const OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z.coerce.number().finite()).optional();
const OptionalFiniteNumberSchema = NumberLikeSchema.pipe(
z.coerce.number<string | number>().finite(),
).optional();
const OptionalIntegerSchema = NumberLikeSchema.pipe(z.coerce.number().int()).optional();
const OptionalIntegerSchema = NumberLikeSchema.pipe(
z.coerce.number<string | number>().int(),
).optional();
const LocalSpeechResolutionSchema = z.object({
includeProviderConfig: z.boolean(),
@@ -85,9 +85,7 @@ function resolveDefaultModelId(role: DefaultModelRole): SherpaOnnxModelId {
export const DEFAULT_LOCAL_STT_MODEL = resolveDefaultModelId("stt");
export const DEFAULT_LOCAL_TTS_MODEL = resolveDefaultModelId("tts");
function createModelIdSchema<T extends string>(
modelIds: readonly T[],
): z.ZodType<T, z.ZodTypeDef, string> {
function createModelIdSchema<T extends string>(modelIds: readonly T[]): z.ZodType<T, string> {
const validIds = new Set<string>(modelIds);
return z
.string()
@@ -21,7 +21,9 @@ const OpenAiTtsModelSchema = z.enum(["tts-1", "tts-1-hd"]);
const NumberLikeSchema = z.union([z.number(), z.string().trim().min(1)]);
const OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z.coerce.number().finite()).optional();
const OptionalFiniteNumberSchema = NumberLikeSchema.pipe(
z.coerce.number<string | number>().finite(),
).optional();
const OptionalTrimmedStringSchema = z
.string()
@@ -0,0 +1,65 @@
import type pino from "pino";
import { ReadableStream } from "node:stream/web";
import pinoLogger from "pino";
import { describe, expect, test } from "vitest";
import {
OpenAITTS,
type OpenAITTSAudioClient,
type OpenAITTSSpeechRequest,
type OpenAITTSSpeechResponse,
} from "./tts.js";
class FakeOpenAITTSClient implements OpenAITTSAudioClient {
public readonly speechRequests: OpenAITTSSpeechRequest[] = [];
public readonly audio: OpenAITTSAudioClient["audio"];
private readonly speechResponse: OpenAITTSSpeechResponse;
constructor(speechResponse: OpenAITTSSpeechResponse) {
this.speechResponse = speechResponse;
this.audio = {
speech: {
create: async (request: OpenAITTSSpeechRequest) => {
this.speechRequests.push(request);
return this.speechResponse;
},
},
};
}
}
function createLogger(): pino.Logger {
return pinoLogger({ enabled: false });
}
describe("OpenAITTS", () => {
test("returns a Node stream from the OpenAI web response body", async () => {
const openaiClient = new FakeOpenAITTSClient({
body: new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(Buffer.from("audio"));
controller.close();
},
}),
});
const tts = new OpenAITTS({ apiKey: "sk-test" }, createLogger(), { openaiClient });
const result = await tts.synthesizeSpeech("hello");
const chunks: Buffer[] = [];
for await (const chunk of result.stream) {
chunks.push(Buffer.from(chunk));
}
expect(Buffer.concat(chunks).toString()).toBe("audio");
expect(result.format).toBe("pcm");
expect(openaiClient.speechRequests).toEqual([
{
model: "tts-1",
voice: "alloy",
input: "hello",
response_format: "pcm",
},
]);
});
});
@@ -1,6 +1,7 @@
import type pino from "pino";
import { OpenAI } from "openai";
import { Readable } from "node:stream";
import type { ReadableStream as NodeReadableStream } from "node:stream/web";
import type { SpeechStreamResult, TextToSpeechProvider } from "../../speech-provider.js";
export type { SpeechStreamResult };
@@ -12,12 +13,35 @@ export interface TTSConfig {
responseFormat?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
}
export interface OpenAITTSSpeechRequest {
model: NonNullable<TTSConfig["model"]>;
voice: NonNullable<TTSConfig["voice"]>;
input: string;
response_format: NonNullable<TTSConfig["responseFormat"]>;
}
export interface OpenAITTSSpeechResponse {
body: unknown;
}
export interface OpenAITTSAudioClient {
audio: {
speech: {
create(request: OpenAITTSSpeechRequest): Promise<OpenAITTSSpeechResponse>;
};
};
}
export interface OpenAITTSOptions {
openaiClient?: OpenAITTSAudioClient;
}
export class OpenAITTS implements TextToSpeechProvider {
private readonly openaiClient: OpenAI;
private readonly config: TTSConfig;
private readonly openaiClient: OpenAITTSAudioClient;
private readonly config: Required<TTSConfig>;
private readonly logger: pino.Logger;
constructor(ttsConfig: TTSConfig, parentLogger: pino.Logger) {
constructor(ttsConfig: TTSConfig, parentLogger: pino.Logger, options: OpenAITTSOptions = {}) {
this.config = {
model: "tts-1",
voice: "alloy",
@@ -25,9 +49,11 @@ export class OpenAITTS implements TextToSpeechProvider {
...ttsConfig,
};
this.logger = parentLogger.child({ module: "agent", provider: "openai", component: "tts" });
this.openaiClient = new OpenAI({
apiKey: ttsConfig.apiKey,
});
this.openaiClient =
options.openaiClient ??
new OpenAI({
apiKey: ttsConfig.apiKey,
});
this.logger.info(
{ voice: this.config.voice, model: this.config.model, format: this.config.responseFormat },
@@ -53,19 +79,17 @@ export class OpenAITTS implements TextToSpeechProvider {
);
const response = await this.openaiClient.audio.speech.create({
model: this.config.model!,
voice: this.config.voice!,
model: this.config.model,
voice: this.config.voice,
input: text,
response_format: this.config.responseFormat as
| "mp3"
| "opus"
| "aac"
| "flac"
| "wav"
| "pcm",
response_format: this.config.responseFormat,
});
const audioStream = response.body as unknown as Readable;
if (!response.body) {
throw new Error("OpenAI speech response did not include an audio stream");
}
const audioStream = Readable.fromWeb(response.body as NodeReadableStream<Uint8Array>);
const duration = Date.now() - startTime;
this.logger.debug({ duration }, "Speech synthesis stream ready");
@@ -1048,7 +1048,7 @@ export class VoiceAssistantWebSocketServer {
serverId: this.serverId,
hostname: getHostname(),
version: this.daemonVersion,
...(this.serverCapabilities ? { capabilities: this.serverCapabilities } : {}),
capabilities: this.serverCapabilities,
features: {
// COMPAT(providersSnapshot): keep optional until all clients rely on snapshot flow.
providersSnapshot: true,
@@ -62,7 +62,7 @@ type RegistryRecord = PersistedProjectRecord | PersistedWorkspaceRecord;
class FileBackedRegistry<TRecord extends RegistryRecord> {
private readonly filePath: string;
private readonly logger: Logger;
private readonly schema: z.ZodType<TRecord, z.ZodTypeDef, unknown>;
private readonly schema: z.ZodType<TRecord, unknown>;
private readonly getId: (record: TRecord) => string;
private loaded = false;
private readonly cache = new Map<string, TRecord>();
@@ -71,7 +71,7 @@ class FileBackedRegistry<TRecord extends RegistryRecord> {
constructor(options: {
filePath: string;
logger: Logger;
schema: z.ZodType<TRecord, z.ZodTypeDef, unknown>;
schema: z.ZodType<TRecord, unknown>;
getId: (record: TRecord) => string;
component: string;
}) {