diff --git a/package-lock.json b/package-lock.json index 705f9dbdf..813464ab8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bradygaster/squad", - "version": "0.8.25-build.6", + "version": "0.8.25-build.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bradygaster/squad", - "version": "0.8.25-build.6", + "version": "0.8.25-build.10", "license": "MIT", "workspaces": [ "packages/*" @@ -30,9 +30,13 @@ "vitest": "^3.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.5.0" } }, + "node_modules/@agentspec/core": { + "resolved": "packages/agentspec-core", + "link": true + }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", @@ -60,6 +64,20 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -80,6 +98,107 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", @@ -3483,6 +3602,159 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typespec/compiler": { + "version": "0.61.2", + "resolved": "https://registry.npmjs.org/@typespec/compiler/-/compiler-0.61.2.tgz", + "integrity": "sha512-6QxYJd09VWssd/BvY+8eBxTVv085s1UNK63FdPrgT2lgI+j8VMMcpNR9m5l1zWlgGDM7sniA/Or8VCdVA6jerg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.24.7", + "ajv": "~8.17.1", + "change-case": "~5.4.4", + "globby": "~14.0.2", + "mustache": "~4.2.0", + "picocolors": "~1.1.0", + "prettier": "~3.3.3", + "prompts": "~2.4.2", + "semver": "^7.6.3", + "temporal-polyfill": "^0.2.5", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.12", + "yaml": "~2.5.1", + "yargs": "~17.7.2" + }, + "bin": { + "tsp": "cmd/tsp.js", + "tsp-server": "cmd/tsp-server.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@typespec/compiler/node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typespec/compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@typespec/compiler/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typespec/compiler/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typespec/compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/compiler/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typespec/compiler/node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@typespec/json-schema": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@typespec/json-schema/-/json-schema-0.61.0.tgz", + "integrity": "sha512-BoId8WRo4iLFJUbXIRX26OdHWaUqJIFYmRUvDnlUJmYtcKbMRMkfYajJM3L+b7AdXjLbHwh4ACjwIuZVU90N/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "~2.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.61.0" + } + }, + "node_modules/@typespec/json-schema/node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", @@ -3871,6 +4143,13 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -4001,8 +4280,8 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -4016,8 +4295,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -4026,8 +4305,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4042,15 +4321,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -4059,8 +4338,8 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4074,8 +4353,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4087,8 +4366,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4577,8 +4856,8 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6" } @@ -5002,6 +5281,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -5163,8 +5459,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -5821,6 +6117,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6653,6 +6959,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6837,6 +7153,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -6960,6 +7289,36 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/protobufjs": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", @@ -7054,8 +7413,18 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -7274,6 +7643,13 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -7528,8 +7904,25 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terminal-size": { - "version": "4.0.1", + "node_modules/temporal-polyfill": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.5.tgz", + "integrity": "sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "temporal-spec": "^0.2.4" + } + }, + "node_modules/temporal-spec": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.4.tgz", + "integrity": "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/terminal-size": { + "version": "4.0.1", "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", "license": "MIT", @@ -8408,6 +8801,40 @@ "node": ">=14.0.0" } }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-protocol/node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", @@ -8415,6 +8842,13 @@ "dev": true, "license": "MIT" }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -8642,8 +9076,8 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": ">=10" } @@ -8668,8 +9102,8 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -8687,8 +9121,8 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": ">=12" } @@ -8697,8 +9131,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -8707,15 +9141,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -8724,8 +9158,8 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8739,8 +9173,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8776,47 +9210,841 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "packages/squad-cli": { - "name": "@bradygaster/squad-cli", - "version": "0.8.25-build.6", - "hasInstallScript": true, + "packages/agentspec-core": { + "name": "@agentspec/core", + "version": "0.1.0", "license": "MIT", - "dependencies": { - "@bradygaster/squad-sdk": "*", - "ink": "^6.8.0", - "react": "^19.2.4", - "vscode-jsonrpc": "^8.2.1" - }, - "bin": { - "squad": "dist/cli-entry.js", - "squad-cli": "dist/cli-entry.js" - }, "devDependencies": { "@types/node": "^22.0.0", - "@types/react": "^19.2.14", - "esbuild": "^0.25.0", - "ink-testing-library": "^4.0.0", - "typescript": "^5.7.0" + "@typespec/compiler": ">=0.60.0 <0.62.0", + "@typespec/json-schema": ">=0.60.0 <0.62.0", + "typescript": "^5.7.0", + "vitest": "^2.0.0" }, + "peerDependencies": { + "@typespec/compiler": ">=0.60.0 <0.62.0" + } + }, + "packages/agentspec-core/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=20" + "node": ">=12" } }, - "packages/squad-sdk": { - "name": "@bradygaster/squad-sdk", - "version": "0.8.25-build.6", + "packages/agentspec-core/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@github/copilot-sdk": "^0.1.32", - "vscode-jsonrpc": "^8.2.1" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "@types/ws": "^8.5.13", - "typescript": "^5.7.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20" + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/agentspec-core/node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "packages/agentspec-core/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "packages/agentspec-core/node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "packages/agentspec-core/node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "packages/agentspec-core/node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/agentspec-core/node_modules/vite-node/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "packages/agentspec-core/node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "packages/agentspec-core/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/agentspec-core/node_modules/vitest/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "packages/squad-cli": { + "name": "@bradygaster/squad-cli", + "version": "0.8.25-build.10", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bradygaster/squad-sdk": "*", + "ink": "^6.8.0", + "react": "^19.2.4", + "vscode-jsonrpc": "^8.2.1" + }, + "bin": { + "squad": "dist/cli-entry.js", + "squad-cli": "dist/cli-entry.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.2.14", + "esbuild": "^0.25.0", + "ink-testing-library": "^4.0.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=22.5.0" + } + }, + "packages/squad-sdk": { + "name": "@bradygaster/squad-sdk", + "version": "0.8.25-build.10", + "license": "MIT", + "dependencies": { + "@github/copilot-sdk": "^0.1.32", + "vscode-jsonrpc": "^8.2.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/ws": "^8.5.13", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=22.5.0" }, "optionalDependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/packages/agentspec-core/.gitignore b/packages/agentspec-core/.gitignore new file mode 100644 index 000000000..b134d3989 --- /dev/null +++ b/packages/agentspec-core/.gitignore @@ -0,0 +1,2 @@ +.agentspec/ +tsp-output.txt diff --git a/packages/agentspec-core/README.md b/packages/agentspec-core/README.md new file mode 100644 index 000000000..35c1dce8b --- /dev/null +++ b/packages/agentspec-core/README.md @@ -0,0 +1,99 @@ +# @agentspec/core + +> **The OpenAPI of agent definitions — framework-agnostic, compiler-validated, narrative-native.** + +`@agentspec/core` is a TypeSpec library that defines 12 universal agent primitives via TypeSpec decorators. Define your agent once in a `.tsp` file. Framework-specific emitters produce native artifacts for each target. + +## Prerequisites + +Before publishing `@agentspec/core@0.1.0`, complete these gates: + +- [ ] Register the `agentspec` npm org (P0 — do this immediately) +- [ ] Enable npm org 2FA enforcement +- [ ] Configure provenance attestation on publish workflow (`npm publish --provenance`) +- [ ] Restrict publish token to GitHub OIDC identity (no static tokens) + +## Install + +```bash +npm install --save-dev @agentspec/core @typespec/compiler +``` + +Peer dependency: `@typespec/compiler >=0.60.0 <0.62.0` + +## The 12 decorators + +| Decorator | Purpose | +|---|---| +| `@agent(id, description)` | Marks a model as an agent; `id` is the wire identifier | +| `@role(title)` | Agent's role/purpose | +| `@version(semver)` | Agent schema version | +| `@instruction(text)` | System prompt ⚠️ omitted from A2A Agent Card by default | +| `@capability(id, description?)` | What this agent can do (repeatable) | +| `@boundary(handles, doesNotHandle)` | Explicit scope declaration | +| `@tool(id, description?)` | External tool at runtime (repeatable) | +| `@knowledge(source, description?)` | Data source (repeatable) | +| `@memory(strategy)` | Persistence strategy | +| `@conversationStarter(prompt)` | Suggested prompt (repeatable) | +| `@inputMode(mode)` | Supported input modality (repeatable) | +| `@outputMode(mode)` | Supported output modality (repeatable) | + +## Quick start + +```typespec +import "@agentspec/core"; +using AgentSpec; + +@agent("flight", "Architecture decisions that compound") +@role("Lead") +@version("0.1.0") +@instruction("You are Flight, the technical lead...") +@capability("architecture-review", "Evaluates system-level design decisions") +@boundary( + handles: "Architecture decisions, PR reviews", + doesNotHandle: "Feature implementation, test writing" +) +@memory(MemoryStrategy.persistent) +@conversationStarter("What's the right architecture for this feature?") +model Flight {} +``` + +Run `tsp compile` → produces `.agentspec/flight-agent-manifest.json`. + +## Security + +⚠️ **Decorator values are committed plaintext.** Do not include: +- Email addresses or phone numbers (PII) +- Secrets, API keys, bearer tokens +- Internal URLs or SharePoint links + +The compiler emits a warning (configurable to error) when PII patterns are detected. + +## A2A Agent Card + +`@instruction` is **omitted from A2A Agent Card output by default**. Opt in: + +```yaml +# tspconfig.yaml +options: + "@agentspec/core": + a2a-publish-instructions: true +``` + +Sensitivity gates: +- `"public"` — card may be served at `/.well-known/agent-card` +- `"internal"` *(default)* — card generated but not published +- `"restricted"` — no card generated + +## TypeSpec version policy + +Peer dep is minor-locked: `>=0.60.0 <0.62.0`. TypeSpec is pre-1.0 and ships breaking changes between minors. When TypeSpec ships a new minor: test, update peer dep range, update lockfile in a single PR. Never float the range. + +## Examples + +- [`examples/weather-agent.tsp`](./examples/weather-agent.tsp) — minimal single-agent example +- [`examples/squad-team.tsp`](./examples/squad-team.tsp) — multi-agent team definition + +## License + +MIT diff --git a/packages/agentspec-core/examples/squad-team.tsp b/packages/agentspec-core/examples/squad-team.tsp new file mode 100644 index 000000000..6ef3054a7 --- /dev/null +++ b/packages/agentspec-core/examples/squad-team.tsp @@ -0,0 +1,92 @@ +import "@agentspec/core"; +using AgentSpec; + +/** + * Squad team example — the actual agents on this project. + * Demonstrates multi-agent definition in a single .tsp file. + * Run: tsp compile examples/squad-team.tsp + * Output: .agentspec/{id}-agent-manifest.json for each agent + */ + +@agent("flight", "Architecture decisions that compound — patterns that make future features easier") +@role("Lead") +@version("0.1.0") +@instruction(""" + You are Flight, the technical lead on this project. Your job is to make + architecture decisions that compound: every choice should open more doors + than it closes. You review PRs for architectural coherence, not style. + You write proposals before code. You never break existing contracts. +""") +@capability("architecture-review", "Evaluates system-level design decisions") +@capability("proposal-authoring", "Writes structured design proposals before implementation") +@boundary( + handles: "Architecture decisions, PR reviews, scope triage, proposal authoring", + doesNotHandle: "Feature implementation, release management, test writing" +) +@memory(MemoryStrategy.persistent) +@conversationStarter("What's the right architecture for this feature?") +@conversationStarter("Review this PR for architectural issues") +model Flight {} + +@agent("eecom", "Core systems — make it work, then make it right") +@role("Core Dev") +@version("0.1.0") +@instruction(""" + You are EECOM, the core dev. You implement the features that matter: + practical, thorough, correct. You write code that compiles, tests that pass, + and implementations that match the spec. When something is ambiguous, you + resolve it by reading the code, not by asking. You work until it works. +""") +@capability("feature-implementation", "Implements well-specified features end-to-end") +@capability("debugging", "Diagnoses and fixes bugs with reproducible test cases") +@capability("scaffolding", "Creates new packages, modules, and boilerplate") +@boundary( + handles: "Feature implementation, bug fixes, scaffolding, TypeScript/Node.js development", + doesNotHandle: "Architecture decisions, release management, security policy" +) +@memory(MemoryStrategy.persistent) +@conversationStarter("Implement this feature from the spec") +@conversationStarter("Here's a failing test — make it pass") +model EECOM {} + +@agent("fido", "Test coverage and quality gates — if it isn't tested, it isn't done") +@role("Tester") +@version("0.1.0") +@capability("test-authoring", "Writes unit, integration, and snapshot tests") +@capability("coverage-analysis", "Identifies untested paths and coverage gaps") +@boundary( + handles: "Test strategy, test authoring, coverage analysis, quality gates", + doesNotHandle: "Feature implementation, deployment, architecture" +) +@memory(MemoryStrategy.session) +@conversationStarter("What test cases are missing for this feature?") +@conversationStarter("Write tests for this module") +model FIDO {} + +@agent("retro", "Security review — trust boundaries, secrets hygiene, audit trail") +@role("Security Reviewer") +@version("0.1.0") +@capability("security-review", "Reviews code for trust boundary violations and secrets leakage") +@capability("pii-audit", "Identifies PII exposure in committed artifacts") +@boundary( + handles: "Security review, secrets hygiene, trust boundary analysis, PII audit", + doesNotHandle: "Feature implementation, test writing, documentation" +) +@memory(MemoryStrategy.persistent) +@conversationStarter("Review this code for security issues") +@conversationStarter("Does this change expose any secrets?") +model RETRO {} + +@agent("control", "API contracts and type integrity — the spec is the law") +@role("API Guardian") +@version("0.1.0") +@capability("contract-review", "Reviews API surfaces for breaking changes and contract violations") +@capability("type-integrity", "Ensures TypeScript types match runtime behavior") +@boundary( + handles: "API contract review, type integrity, breaking change analysis", + doesNotHandle: "Feature implementation, security, deployment" +) +@memory(MemoryStrategy.persistent) +@conversationStarter("Does this change break any existing contracts?") +@conversationStarter("Review the types in this PR") +model CONTROL {} diff --git a/packages/agentspec-core/examples/weather-agent.tsp b/packages/agentspec-core/examples/weather-agent.tsp new file mode 100644 index 000000000..e0e947ea9 --- /dev/null +++ b/packages/agentspec-core/examples/weather-agent.tsp @@ -0,0 +1,33 @@ +import "@agentspec/core"; +using AgentSpec; + +/** + * Minimal weather agent example — illustrates the core decorator API. + * Run: tsp compile examples/weather-agent.tsp + * Output: .agentspec/weather-agent-manifest.json + */ + +@agent("weather-agent", "Provides real-time weather data and forecasts for any location") +@role("Weather Specialist") +@version("0.1.0") +@instruction(""" + You are a weather assistant. Answer questions about current conditions, + forecasts, and historical weather data. Always cite your data source. + Provide temperatures in both Celsius and Fahrenheit unless the user specifies. +""") +@capability("current-conditions", "Real-time weather for a given location") +@capability("forecast", "Multi-day weather forecasts up to 10 days") +@capability("historical", "Historical weather data and averages") +@boundary( + "Weather queries, forecasts, climate data, severe weather alerts", + "Air quality, UV index, astronomical events, sports or travel recommendations" +) +@tool("openweathermap", "OpenWeatherMap API for current conditions and forecasts") +@tool("noaa", "NOAA API for historical data and severe weather alerts") +@memory(MemoryStrategy.session) +@conversationStarter("What's the weather like in Seattle right now?") +@conversationStarter("Will it rain this weekend in Chicago?") +@inputMode(InputMode.text) +@outputMode(OutputMode.text) +@outputMode(OutputMode.structured) +model WeatherAgent {} diff --git a/packages/agentspec-core/generated/agent-manifest.schema.json b/packages/agentspec-core/generated/agent-manifest.schema.json new file mode 100644 index 000000000..29731f267 --- /dev/null +++ b/packages/agentspec-core/generated/agent-manifest.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://agentspec.dev/schemas/agent-manifest/0.1.json", + "title": "AgentManifest", + "description": "The @agentspec/core agent manifest schema. Generated from model AgentManifest in lib/main.tsp.", + "type": "object", + "required": ["specVersion", "id", "description", "sensitivity", "behavior", "runtime", "communication"], + "properties": { + "$schema": { "type": "string" }, + "specVersion": { + "type": "string", + "description": "Protocol version — bumps only on breaking schema changes. Distinct from package semver." + }, + "id": { "type": "string" }, + "description": { "type": "string" }, + "role": { "type": "string" }, + "agentVersion": { "type": "string" }, + "sensitivity": { + "type": "string", + "enum": ["public", "internal", "restricted"], + "default": "internal" + }, + "behavior": { + "type": "object", + "required": ["capabilities"], + "properties": { + "instructions": { "type": "string" }, + "capabilities": { + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "level": { "type": "string" } + } + } + }, + "boundaries": { + "type": "object", + "required": ["handles", "doesNotHandle"], + "properties": { + "handles": { "type": "string" }, + "doesNotHandle": { "type": "string" } + } + } + } + }, + "runtime": { + "type": "object", + "required": ["tools", "knowledge", "memory"], + "properties": { + "tools": { + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" } + } + } + }, + "knowledge": { + "type": "array", + "items": { + "type": "object", + "required": ["source"], + "properties": { + "source": { "type": "string" }, + "description": { "type": "string" } + } + } + }, + "memory": { + "type": "string", + "enum": ["none", "session", "persistent", "shared"] + } + } + }, + "communication": { + "type": "object", + "required": ["conversationStarters", "inputModes", "outputModes"], + "properties": { + "conversationStarters": { + "type": "array", + "items": { "type": "string" } + }, + "inputModes": { + "type": "array", + "items": { "type": "string", "enum": ["text", "file", "image", "audio", "structured"] } + }, + "outputModes": { + "type": "array", + "items": { "type": "string", "enum": ["text", "file", "image", "audio", "structured", "stream"] } + } + } + } + }, + "additionalProperties": false, + "$comment": "This schema is generated from model AgentManifest in lib/main.tsp. Do not edit by hand — regenerate with: tsp compile --emit @typespec/json-schema" +} diff --git a/packages/agentspec-core/lib/main.tsp b/packages/agentspec-core/lib/main.tsp new file mode 100644 index 000000000..3bc94e502 --- /dev/null +++ b/packages/agentspec-core/lib/main.tsp @@ -0,0 +1,143 @@ +import "@typespec/compiler"; +import "../dist/decorators.js"; + +namespace AgentSpec; + +using TypeSpec.Reflection; + +// ─── Decorator declarations ─────────────────────────────────────────────────── + +/** Marks a model as an agent definition. `id` is the wire identifier. */ +extern dec agent(target: Model, id: valueof string, description: valueof string); + +/** Declares the agent's role/title — maps to CrewAI `role`, SK description, M365 `description`. */ +extern dec role(target: Model, title: valueof string); + +/** Declares the agent schema version (distinct from specVersion / protocol version). */ +extern dec version(target: Model, semver: valueof string); + +/** + * Declares the agent's system prompt / behavioral instruction. + * ⚠️ Omitted from A2A Agent Card output by default. + * ⚠️ Do NOT include secrets, PII, or internal URLs — these values are committed plaintext. + */ +extern dec instruction(target: Model, text: valueof string); + +/** Declares a capability this agent provides. Repeatable. */ +extern dec capability(target: Model, id: valueof string, description?: valueof string, level?: valueof string); + +/** Declares scope boundaries — what this agent handles and what it explicitly does not. */ +extern dec boundary(target: Model, handles: valueof string, doesNotHandle: valueof string); + +/** Declares an external tool available at runtime. Repeatable. */ +extern dec tool(target: Model, id: valueof string, description?: valueof string); + +/** + * Declares a knowledge source (data store, index, connector). Repeatable. + * ⚠️ Do NOT use internal URLs — these values are committed plaintext. + */ +extern dec knowledge(target: Model, source: valueof string, description?: valueof string); + +/** Declares the agent's memory persistence strategy. */ +extern dec memory(target: Model, strategy: valueof MemoryStrategy); + +/** Declares a suggested conversation starter prompt. Repeatable. */ +extern dec conversationStarter(target: Model, prompt: valueof string); + +/** Declares a supported input modality. Repeatable. */ +extern dec inputMode(target: Model, mode: valueof InputMode); + +/** Declares a supported output modality. Repeatable. */ +extern dec outputMode(target: Model, mode: valueof OutputMode); + +/** Declares the sensitivity/visibility level of this agent. Defaults to "internal". */ +extern dec sensitivity(target: Model, level: valueof SensitivityLevel); + +// ─── Enums ─────────────────────────────────────────────────────────────────── + +enum MemoryStrategy { + none, + session, + persistent, + shared, +} + +enum InputMode { + text, + file, + image, + audio, + structured, +} + +enum OutputMode { + text, + file, + image, + audio, + structured, + stream, +} + +enum SensitivityLevel { + public, + internal, + restricted, +} + +// ─── AgentManifest model ───────────────────────────────────────────────────── +// This model is the canonical shape of agent-manifest.json. +// @typespec/json-schema derives the schema from this model — not from the emitter. + +model AgentManifest { + `$schema`?: string; + specVersion: string; + id: string; + description: string; + role?: string; + agentVersion?: string; + sensitivity: SensitivityLevel; + behavior: AgentBehavior; + runtime: AgentRuntime; + communication: AgentCommunication; +} + +model AgentBehavior { + instructions?: string; + capabilities: AgentCapability[]; + boundaries?: AgentBoundaries; +} + +model AgentCapability { + id: string; + description?: string; + /** Optional proficiency level: "expert" | "proficient" | "basic" */ + level?: string; +} + +model AgentBoundaries { + handles: string; + doesNotHandle: string; +} + +model AgentRuntime { + tools: AgentTool[]; + knowledge: AgentKnowledge[]; + memory: MemoryStrategy; +} + +model AgentTool { + id: string; + description?: string; +} + +model AgentKnowledge { + source: string; + description?: string; +} + +model AgentCommunication { + conversationStarters: string[]; + inputModes: InputMode[]; + outputModes: OutputMode[]; +} diff --git a/packages/agentspec-core/package.json b/packages/agentspec-core/package.json new file mode 100644 index 000000000..1649f1aaa --- /dev/null +++ b/packages/agentspec-core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@agentspec/core", + "version": "0.1.0", + "description": "Framework-agnostic TypeSpec library for defining agents — the OpenAPI of agent definitions", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "tspMain": "lib/main.tsp", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "lib", + "generated", + "README.md" + ], + "scripts": { + "compile": "tsc -p tsconfig.json", + "build": "tsc -p tsconfig.json", + "test": "vitest run" + }, + "peerDependencies": { + "@typespec/compiler": ">=0.60.0 <0.62.0" + }, + "devDependencies": { + "@typespec/compiler": ">=0.60.0 <0.62.0", + "@typespec/json-schema": ">=0.60.0 <0.62.0", + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.0.0" + }, + "keywords": [ + "typespec", + "agent", + "agentspec", + "ai", + "multi-agent", + "openapi-for-agents" + ], + "license": "MIT", + "homepage": "https://github.com/bradygaster/squad#readme", + "bugs": { + "url": "https://github.com/bradygaster/squad/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bradygaster/squad.git", + "directory": "packages/agentspec-core" + } +} diff --git a/packages/agentspec-core/src/decorators.ts b/packages/agentspec-core/src/decorators.ts new file mode 100644 index 000000000..66012ca09 --- /dev/null +++ b/packages/agentspec-core/src/decorators.ts @@ -0,0 +1,177 @@ +import type { + DecoratorContext, + Model, +} from "@typespec/compiler"; +import { StateKeys } from "./lib.js"; +import { checkForPii } from "./diagnostics.js"; + +/** Extract a plain string from a valueof enum param. + * TypeSpec 0.56+ passes an EnumValue { valueKind, value: EnumMember } at runtime. */ +function enumName(v: unknown): string { + if (typeof v === "string") return v; + const obj = v as Record; + if (obj.valueKind === "EnumValue") { + return (obj.value as { name: string }).name; + } + if (typeof obj.name === "string") return obj.name; + return String(v); +} + +export const namespace = "AgentSpec"; + +// ─── State key types ───────────────────────────────────────────────────────── + +export interface AgentState { + readonly id: string; + readonly description: string; +} + +export interface CapabilityEntry { + readonly id: string; + readonly description?: string; + readonly level?: string; +} + +export interface ToolEntry { + readonly id: string; + readonly description?: string; +} + +export interface KnowledgeEntry { + readonly source: string; + readonly description?: string; +} + +export interface BoundaryState { + readonly handles: string; + readonly doesNotHandle: string; +} + +// ─── Decorator implementations ─────────────────────────────────────────────── + +export function $agent( + ctx: DecoratorContext, + target: Model, + id: string, + description: string +): void { + ctx.program.stateMap(StateKeys.agent).set(target, { id, description }); + ctx.program.stateSet(StateKeys.agentSet).add(target); +} + +export function $role( + ctx: DecoratorContext, + target: Model, + title: string +): void { + ctx.program.stateMap(StateKeys.role).set(target, title); +} + +export function $version( + ctx: DecoratorContext, + target: Model, + semver: string +): void { + ctx.program.stateMap(StateKeys.version).set(target, semver); +} + +export function $instruction( + ctx: DecoratorContext, + target: Model, + text: string +): void { + checkForPii(ctx.program, text, target); + ctx.program.stateMap(StateKeys.instruction).set(target, text); +} + +export function $capability( + ctx: DecoratorContext, + target: Model, + id: string, + description?: string, + level?: string +): void { + const map = ctx.program.stateMap(StateKeys.capabilities); + const existing: CapabilityEntry[] = map.get(target) ?? []; + map.set(target, [...existing, { id, description, level }]); +} + +export function $boundary( + ctx: DecoratorContext, + target: Model, + handles: string, + doesNotHandle: string +): void { + ctx.program.stateMap(StateKeys.boundary).set(target, { handles, doesNotHandle }); +} + +export function $tool( + ctx: DecoratorContext, + target: Model, + id: string, + description?: string +): void { + const map = ctx.program.stateMap(StateKeys.tools); + const existing: ToolEntry[] = map.get(target) ?? []; + map.set(target, [...existing, { id, description }]); +} + +export function $knowledge( + ctx: DecoratorContext, + target: Model, + source: string, + description?: string +): void { + checkForPii(ctx.program, source, target); + if (description !== undefined) checkForPii(ctx.program, description, target); + const map = ctx.program.stateMap(StateKeys.knowledge); + const existing: KnowledgeEntry[] = map.get(target) ?? []; + map.set(target, [...existing, { source, description }]); +} + +export function $memory( + ctx: DecoratorContext, + target: Model, + strategy: unknown +): void { + ctx.program.stateMap(StateKeys.memory).set(target, enumName(strategy)); +} + +export function $conversationStarter( + ctx: DecoratorContext, + target: Model, + prompt: string +): void { + checkForPii(ctx.program, prompt, target); + const map = ctx.program.stateMap(StateKeys.conversationStarters); + const existing: string[] = map.get(target) ?? []; + map.set(target, [...existing, prompt]); +} + +export function $inputMode( + ctx: DecoratorContext, + target: Model, + mode: unknown +): void { + const map = ctx.program.stateMap(StateKeys.inputModes); + const existing: string[] = map.get(target) ?? []; + map.set(target, [...existing, enumName(mode)]); +} + +export function $outputMode( + ctx: DecoratorContext, + target: Model, + mode: unknown +): void { + const map = ctx.program.stateMap(StateKeys.outputModes); + const existing: string[] = map.get(target) ?? []; + map.set(target, [...existing, enumName(mode)]); +} + +export function $sensitivity( + ctx: DecoratorContext, + target: Model, + level: unknown +): void { + ctx.program.stateMap(StateKeys.sensitivity).set(target, enumName(level)); +} diff --git a/packages/agentspec-core/src/diagnostics.ts b/packages/agentspec-core/src/diagnostics.ts new file mode 100644 index 000000000..6c9606492 --- /dev/null +++ b/packages/agentspec-core/src/diagnostics.ts @@ -0,0 +1,31 @@ +import type { Model, Program } from "@typespec/compiler"; +import { reportDiagnostic } from "./lib.js"; + +// ─── PII detection patterns ─────────────────────────────────────────────────── + +const PII_PATTERNS: ReadonlyArray<{ readonly name: string; readonly pattern: RegExp }> = [ + { name: "email", pattern: /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/ }, + { name: "phone", pattern: /\+?[\d\s\-\(\)]{10,}/ }, + { name: "bearer-token", pattern: /sk-|Bearer |ghp_|token:/ }, + { name: "sas-url", pattern: /sig=|sv=|se=/ }, +]; + +/** + * Check a decorator string value for PII patterns and report a compiler + * warning if any match. Configurable to `error` level via tspconfig.yaml. + */ +export function checkForPii(program: Program, value: string, target: Model): void { + for (const { pattern } of PII_PATTERNS) { + if (pattern.test(value)) { + reportDiagnostic(program, { + code: "pii-in-decorator", + target, + messageId: "default", + format: {}, + }); + return; // one warning per value is enough + } + } +} + +export { PII_PATTERNS }; diff --git a/packages/agentspec-core/src/emitter.ts b/packages/agentspec-core/src/emitter.ts new file mode 100644 index 000000000..2e93239c1 --- /dev/null +++ b/packages/agentspec-core/src/emitter.ts @@ -0,0 +1,112 @@ +import type { EmitContext, Model, Type } from "@typespec/compiler"; +import { navigateProgram } from "@typespec/compiler"; +import { StateKeys, AGENTSPEC_PROTOCOL_VERSION, reportDiagnostic } from "./lib.js"; +import type { AgentManifestData } from "./types.js"; + +export async function $onEmit(ctx: EmitContext): Promise { + const { program } = ctx; + const outputDir = ctx.emitterOutputDir; + + const agentSet: Set = program.stateSet(StateKeys.agentSet); + const writeOps: Promise[] = []; + + await program.host.mkdirp(outputDir); + + navigateProgram(program, { + model: (model) => { + // CRITICAL: filter by stateSet — built-in types like string/int32 must be skipped + if (!agentSet.has(model)) return; + + const agentState = program.stateMap(StateKeys.agent).get(model) as + | { id: string; description: string } + | undefined; + + if (!agentState) return; + + // Security: reject agent IDs with path traversal sequences + if (agentState.id.includes('..') || agentState.id.includes('/') || agentState.id.includes('\\')) { + reportDiagnostic(program, { + code: "path-traversal", + target: model, + messageId: "default", + format: {}, + }); + return; + } + + const manifest = buildManifest(program, model, agentState); + const fileName = `${agentState.id}-agent-manifest.json`; + const outputPath = `${outputDir}/${fileName}`; + + writeOps.push(program.host.writeFile(outputPath, JSON.stringify(manifest, null, 2))); + }, + }); + + await Promise.all(writeOps); +} + +function buildManifest( + program: EmitContext["program"], + model: Model, + agentState: { readonly id: string; readonly description: string } +): AgentManifestData { + const role = program.stateMap(StateKeys.role).get(model) as string | undefined; + const agentVersion = program.stateMap(StateKeys.version).get(model) as string | undefined; + const instructions = program.stateMap(StateKeys.instruction).get(model) as string | undefined; + const capabilities = (program.stateMap(StateKeys.capabilities).get(model) ?? []) as ReadonlyArray<{ + readonly id: string; + readonly description?: string; + readonly level?: string; + }>; + const boundary = program.stateMap(StateKeys.boundary).get(model) as + | { readonly handles: string; readonly doesNotHandle: string } + | undefined; + const tools = (program.stateMap(StateKeys.tools).get(model) ?? []) as ReadonlyArray<{ + readonly id: string; + readonly description?: string; + }>; + const knowledge = (program.stateMap(StateKeys.knowledge).get(model) ?? []) as ReadonlyArray<{ + readonly source: string; + readonly description?: string; + }>; + const memory = (program.stateMap(StateKeys.memory).get(model) as string | undefined) ?? "none"; + const sensitivityRaw = program.stateMap(StateKeys.sensitivity).get(model) as string | undefined; + const sensitivity = (sensitivityRaw ?? "internal") as "public" | "internal" | "restricted"; + const conversationStarters = (program.stateMap(StateKeys.conversationStarters).get(model) ?? []) as string[]; + const inputModes = (program.stateMap(StateKeys.inputModes).get(model) ?? ["text"]) as string[]; + const outputModes = (program.stateMap(StateKeys.outputModes).get(model) ?? ["text"]) as string[]; + + return { + $schema: "https://agentspec.dev/schemas/agent-manifest/0.1.json", + specVersion: AGENTSPEC_PROTOCOL_VERSION, + id: agentState.id, + description: agentState.description, + ...(role !== undefined && { role }), + ...(agentVersion !== undefined && { agentVersion }), + sensitivity, + behavior: { + ...(instructions !== undefined && { instructions }), + capabilities: capabilities.map((c) => ({ + id: c.id, + ...(c.description && { description: c.description }), + ...(c.level && { level: c.level }), + })), + ...(boundary !== undefined && { + boundaries: { + handles: boundary.handles, + doesNotHandle: boundary.doesNotHandle, + }, + }), + }, + runtime: { + tools: tools.map((t) => ({ id: t.id, ...(t.description && { description: t.description }) })), + knowledge: knowledge.map((k) => ({ source: k.source, ...(k.description && { description: k.description }) })), + memory, + }, + communication: { + conversationStarters, + inputModes, + outputModes, + }, + }; +} diff --git a/packages/agentspec-core/src/index.ts b/packages/agentspec-core/src/index.ts new file mode 100644 index 000000000..43674d5af --- /dev/null +++ b/packages/agentspec-core/src/index.ts @@ -0,0 +1,20 @@ +export { $onEmit } from "./emitter.js"; +export { StateKeys, AGENTSPEC_PROTOCOL_VERSION, $lib } from "./lib.js"; +export { toAgentCard } from "./translators/a2a.js"; +export type { AgentManifestData } from "./types.js"; +export type { AgentCard, AgentCardSkill } from "./translators/a2a.js"; +export { + $agent, + $role, + $version, + $instruction, + $capability, + $boundary, + $tool, + $knowledge, + $memory, + $conversationStarter, + $inputMode, + $outputMode, + $sensitivity, +} from "./decorators.js"; diff --git a/packages/agentspec-core/src/lib.ts b/packages/agentspec-core/src/lib.ts new file mode 100644 index 000000000..80ac77b35 --- /dev/null +++ b/packages/agentspec-core/src/lib.ts @@ -0,0 +1,63 @@ +import { createTypeSpecLibrary } from "@typespec/compiler"; + +// ─── Protocol version ───────────────────────────────────────────────────────── +// Bumps only on breaking schema changes — independent of npm package semver. +export const AGENTSPEC_PROTOCOL_VERSION = "0.1.0"; + +// ─── State keys ─────────────────────────────────────────────────────────────── + +export const StateKeys = { + /** stateMap: Model → { id, description } */ + agent: Symbol.for("@agentspec/core::agent"), + /** stateSet: Set — used by navigateProgram filter to skip built-ins */ + agentSet: Symbol.for("@agentspec/core::agentSet"), + /** stateMap: Model → string */ + role: Symbol.for("@agentspec/core::role"), + /** stateMap: Model → string */ + version: Symbol.for("@agentspec/core::version"), + /** stateMap: Model → string */ + instruction: Symbol.for("@agentspec/core::instruction"), + /** stateMap: Model → CapabilityEntry[] */ + capabilities: Symbol.for("@agentspec/core::capabilities"), + /** stateMap: Model → BoundaryState */ + boundary: Symbol.for("@agentspec/core::boundary"), + /** stateMap: Model → ToolEntry[] */ + tools: Symbol.for("@agentspec/core::tools"), + /** stateMap: Model → KnowledgeEntry[] */ + knowledge: Symbol.for("@agentspec/core::knowledge"), + /** stateMap: Model → string (MemoryStrategy value) */ + memory: Symbol.for("@agentspec/core::memory"), + /** stateMap: Model → string[] */ + conversationStarters: Symbol.for("@agentspec/core::conversationStarters"), + /** stateMap: Model → string[] (InputMode values) */ + inputModes: Symbol.for("@agentspec/core::inputModes"), + /** stateMap: Model → string[] (OutputMode values) */ + outputModes: Symbol.for("@agentspec/core::outputModes"), + /** stateMap: Model → string (SensitivityLevel value) */ + sensitivity: Symbol.for("@agentspec/core::sensitivity"), +} as const; + +// ─── Library definition ─────────────────────────────────────────────────────── + +const libDef = { + name: "@agentspec/core", + diagnostics: { + "pii-in-decorator": { + severity: "warning", + messages: { + default: "Possible PII or sensitive value detected in decorator argument. " + + "Do not commit secrets, email addresses, phone numbers, or internal URLs. " + + "All decorator values are serialized to plaintext artifacts in git history.", + }, + }, + "path-traversal": { + severity: "error", + messages: { + default: "Agent ID contains path traversal sequences ('..', '/', or '\\'). Manifest will not be emitted.", + }, + }, + }, +} as const; + +export const $lib = createTypeSpecLibrary(libDef); +export const { reportDiagnostic } = $lib; diff --git a/packages/agentspec-core/src/translators/a2a.ts b/packages/agentspec-core/src/translators/a2a.ts new file mode 100644 index 000000000..2175e7f34 --- /dev/null +++ b/packages/agentspec-core/src/translators/a2a.ts @@ -0,0 +1,64 @@ +import type { AgentManifestData } from "../types.js"; + +// ─── Google A2A Agent Card types ────────────────────────────────────────────── + +export interface AgentCard { + readonly name: string; + readonly description: string; + readonly instructions?: string; + readonly skills: ReadonlyArray; + readonly defaultInputModes: readonly string[]; + readonly defaultOutputModes: readonly string[]; +} + +export interface AgentCardSkill { + readonly id: string; + readonly name: string; + readonly description?: string; + readonly examples?: readonly string[]; +} + +// ─── Translator ─────────────────────────────────────────────────────────────── + +export interface ToAgentCardOptions { + /** + * Whether to include `behavior.instructions` in the Agent Card output. + * Default: false (instructions are behavioral config, not discovery metadata). + */ + readonly publishInstructions?: boolean; +} + +/** + * Map an AgentManifestData to a Google A2A Agent Card. + * + * Sensitivity gating: + * - "public" → card generated and may be published + * - "internal" → card generated but not for external publishing + * - "restricted" → returns null; no card generated + */ +export function toAgentCard( + manifest: AgentManifestData, + options: ToAgentCardOptions = {} +): AgentCard | null { + if (manifest.sensitivity === "restricted") { + return null; + } + + const skills: AgentCardSkill[] = manifest.behavior.capabilities.map((cap) => ({ + id: cap.id, + name: cap.id, + ...(cap.description && { description: cap.description }), + examples: manifest.communication.conversationStarters.slice() as string[], + })); + + return { + name: manifest.id, + description: manifest.description, + ...(options.publishInstructions && manifest.behavior.instructions !== undefined && { + instructions: manifest.behavior.instructions, + }), + skills, + defaultInputModes: manifest.communication.inputModes.slice() as string[], + defaultOutputModes: manifest.communication.outputModes.slice() as string[], + }; +} diff --git a/packages/agentspec-core/src/types.ts b/packages/agentspec-core/src/types.ts new file mode 100644 index 000000000..de1ca2c2a --- /dev/null +++ b/packages/agentspec-core/src/types.ts @@ -0,0 +1,41 @@ +// Intermediate TypeScript representation of agent-manifest.json. +// The canonical shape lives in lib/main.tsp as `model AgentManifest`. +// This interface mirrors that model for use within the emitter and translators. + +export interface AgentManifestData { + readonly $schema?: string; + readonly specVersion: string; + readonly id: string; + readonly description: string; + readonly role?: string; + readonly agentVersion?: string; + readonly sensitivity: "public" | "internal" | "restricted"; + readonly behavior: { + readonly instructions?: string; + readonly capabilities: ReadonlyArray<{ + readonly id: string; + readonly description?: string; + readonly level?: string; + }>; + readonly boundaries?: { + readonly handles: string; + readonly doesNotHandle: string; + }; + }; + readonly runtime: { + readonly tools: ReadonlyArray<{ + readonly id: string; + readonly description?: string; + }>; + readonly knowledge: ReadonlyArray<{ + readonly source: string; + readonly description?: string; + }>; + readonly memory: string; + }; + readonly communication: { + readonly conversationStarters: readonly string[]; + readonly inputModes: readonly string[]; + readonly outputModes: readonly string[]; + }; +} diff --git a/packages/agentspec-core/test/unit.test.ts b/packages/agentspec-core/test/unit.test.ts new file mode 100644 index 000000000..2cce9d34b --- /dev/null +++ b/packages/agentspec-core/test/unit.test.ts @@ -0,0 +1,203 @@ +import { describe, it, expect, vi } from "vitest"; +import type { Program, Model } from "@typespec/compiler"; +import { toAgentCard } from "../src/translators/a2a.js"; +import { checkForPii, PII_PATTERNS } from "../src/diagnostics.js"; +import type { AgentManifestData } from "../src/types.js"; + +// ─── Fixtures ──────────────────────────────────────────────────────────────── + +function makeManifest(overrides: Partial = {}): AgentManifestData { + return { + $schema: "https://agentspec.dev/schemas/agent-manifest/0.1.json", + specVersion: "0.1.0", + id: "test-agent", + description: "A test agent", + sensitivity: "internal", + behavior: { + instructions: "You are a helpful agent.", + capabilities: [ + { id: "cap-1", description: "First capability" }, + { id: "cap-2" }, + ], + }, + runtime: { tools: [], knowledge: [], memory: "none" }, + communication: { + conversationStarters: ["Hello", "Help me"], + inputModes: ["text"], + outputModes: ["text"], + }, + ...overrides, + }; +} + +function makeMockProgram() { + const diagnostics: unknown[] = []; + const mock = { reportDiagnostic: (d: unknown) => diagnostics.push(d) } as unknown as Program; + return { mock, diagnostics }; +} + +const mockTarget = {} as unknown as Model; + +// ─── toAgentCard ───────────────────────────────────────────────────────────── + +describe("toAgentCard()", () => { + it("returns a card with correct shape for a basic manifest", () => { + const card = toAgentCard(makeManifest()); + expect(card).not.toBeNull(); + expect(card!.name).toBe("test-agent"); + expect(card!.description).toBe("A test agent"); + expect(card!.skills).toHaveLength(2); + expect(card!.skills[0]!.id).toBe("cap-1"); + expect(card!.defaultInputModes).toContain("text"); + expect(card!.defaultOutputModes).toContain("text"); + }); + + it("omits instructions by default (publishInstructions not set)", () => { + const card = toAgentCard(makeManifest()); + expect(card).not.toBeNull(); + expect((card as any).instructions).toBeUndefined(); + }); + + it("includes instructions when publishInstructions: true", () => { + const card = toAgentCard(makeManifest(), { publishInstructions: true }); + expect(card).not.toBeNull(); + expect(card!.instructions).toBe("You are a helpful agent."); + }); + + it("omits instructions when publishInstructions: true but no instructions in manifest", () => { + const manifest = makeManifest({ behavior: { capabilities: [] } }); + const card = toAgentCard(manifest, { publishInstructions: true }); + expect(card).not.toBeNull(); + expect((card as any).instructions).toBeUndefined(); + }); + + it("omits instructions when publishInstructions: false", () => { + const card = toAgentCard(makeManifest(), { publishInstructions: false }); + expect(card).not.toBeNull(); + expect((card as any).instructions).toBeUndefined(); + }); + + it("returns null for sensitivity 'restricted'", () => { + const card = toAgentCard(makeManifest({ sensitivity: "restricted" })); + expect(card).toBeNull(); + }); + + it("returns a card for sensitivity 'public'", () => { + const card = toAgentCard(makeManifest({ sensitivity: "public" })); + expect(card).not.toBeNull(); + }); + + it("populates skill examples from conversationStarters", () => { + const card = toAgentCard(makeManifest()); + expect(card!.skills[0]!.examples).toEqual(["Hello", "Help me"]); + }); +}); + +// ─── checkForPii ───────────────────────────────────────────────────────────── + +describe("checkForPii()", () => { + it("fires a diagnostic for an email address", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "Contact admin@example.com for help", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("fires a diagnostic for a bearer token", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "Authorization: Bearer abc123xyz", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("fires a diagnostic for a GitHub PAT prefix", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "Use ghp_mytoken123 to authenticate", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("fires a diagnostic for a phone number pattern", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "Call +1 (555) 867-5309", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("fires a diagnostic for an sk- token", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "sk-proj-abc123", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("fires only one diagnostic per value even if multiple patterns match", () => { + const { mock, diagnostics } = makeMockProgram(); + // email + token both match + checkForPii(mock, "sk-abc user@test.com", mockTarget); + expect(diagnostics).toHaveLength(1); + }); + + it("does not fire for a clean string", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "You are a helpful assistant that answers questions.", mockTarget); + expect(diagnostics).toHaveLength(0); + }); + + it("does not fire for an empty string", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "", mockTarget); + expect(diagnostics).toHaveLength(0); + }); + + it("does not fire for a normal role description", () => { + const { mock, diagnostics } = makeMockProgram(); + checkForPii(mock, "Search the web for up-to-date information and summarize results.", mockTarget); + expect(diagnostics).toHaveLength(0); + }); +}); + +// ─── PII_PATTERNS ───────────────────────────────────────────────────────────── + +describe("PII_PATTERNS", () => { + it("email pattern matches user@domain.tld", () => { + const ep = PII_PATTERNS.find((p) => p.name === "email"); + expect(ep?.pattern.test("user@example.com")).toBe(true); + }); + + it("bearer-token pattern matches 'token:' prefix", () => { + const bt = PII_PATTERNS.find((p) => p.name === "bearer-token"); + expect(bt?.pattern.test("token:mysecret")).toBe(true); + }); + + it("sas-url pattern matches Azure SAS signature fragment", () => { + const sas = PII_PATTERNS.find((p) => p.name === "sas-url"); + expect(sas?.pattern.test("https://storage.blob.core.windows.net/x?sv=2021&sig=abc")).toBe(true); + }); +}); + +// ─── Path traversal guard logic ─────────────────────────────────────────────── + +describe("path traversal rejection patterns", () => { + const isSafe = (id: string) => + !id.includes("..") && !id.includes("/") && !id.includes("\\"); + + it("rejects double-dot sequences", () => { + expect(isSafe("../etc/passwd")).toBe(false); + }); + + it("rejects forward slashes", () => { + expect(isSafe("agents/my-agent")).toBe(false); + }); + + it("rejects backslashes", () => { + expect(isSafe("agents\\my-agent")).toBe(false); + }); + + it("rejects combined traversal", () => { + expect(isSafe("..\\windows\\system32")).toBe(false); + }); + + it("accepts clean hyphenated id", () => { + expect(isSafe("my-agent-v2")).toBe(true); + }); + + it("accepts alphanumeric id with underscores", () => { + expect(isSafe("support_bot_01")).toBe(true); + }); +}); diff --git a/packages/agentspec-core/tsconfig.json b/packages/agentspec-core/tsconfig.json new file mode 100644 index 000000000..16ef20c60 --- /dev/null +++ b/packages/agentspec-core/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "esModuleInterop": false, + "skipLibCheck": true, + "composite": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/agentspec-core/tspconfig.yaml b/packages/agentspec-core/tspconfig.yaml new file mode 100644 index 000000000..6bebd9b5c --- /dev/null +++ b/packages/agentspec-core/tspconfig.yaml @@ -0,0 +1,9 @@ +emit: + - "@agentspec/core" + +options: + "@agentspec/core": + emitter-output-dir: "{project-root}/.agentspec" + # Set to true to include behavior.instructions in A2A Agent Card output. + # Default: false — system prompts are behavioral config, not discovery metadata. + a2a-publish-instructions: false