diff --git a/index.ts b/index.ts index 851c478..e4bef53 100644 --- a/index.ts +++ b/index.ts @@ -330,8 +330,9 @@ const plugin = { const pushStore = new PushNotificationStore(); const client = new A2AClient(); const taskStore = new FileTaskStore(config.storage.tasksDir); + const agentExecutor = new OpenClawAgentExecutor(api, config); const executor = new QueueingAgentExecutor( - new OpenClawAgentExecutor(api, config), + agentExecutor, telemetry, config.limits, config.routing.defaultAgentId, @@ -981,6 +982,7 @@ const plugin = { healthManager?.stop(); auditLogger.close(); client.destroy(); + agentExecutor.close(); // Stop task cleanup timer if (cleanupTimer) { diff --git a/package-lock.json b/package-lock.json index 8cac269..de83148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,21 @@ { - "name": "a2a-gateway", - "version": "1.0.1", + "name": "openclaw-a2a-gateway", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "a2a-gateway", - "version": "1.0.1", + "name": "openclaw-a2a-gateway", + "version": "1.4.0", + "license": "MIT", "dependencies": { "@a2a-js/sdk": "^0.3.13", "@bufbuild/protobuf": "^2.11.0", "@grpc/grpc-js": "^1.14.3", "express": "^4.21.2", "multicast-dns": "^7.2.5", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "ws": "^8.20.0" }, "devDependencies": { "@types/express": "^5.0.6", @@ -932,12 +934,34 @@ "scripts/actions/documentation" ] }, + "node_modules/@buape/carbon/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)", - "peer": true + "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@cacheable/memory": { "version": "2.0.8", @@ -1007,163 +1031,6 @@ "license": "MIT OR Apache-2.0", "optional": true }, - "node_modules/@discordjs/node-pre-gyp": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@discordjs/node-pre-gyp/-/node-pre-gyp-0.4.5.tgz", - "integrity": "sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "optional": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@discordjs/node-pre-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/@discordjs/voice": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.0.tgz", @@ -1184,6 +1051,15 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/voice/node_modules/opusscript": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz", + "integrity": "sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@discordjs/voice/node_modules/prism-media": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz", @@ -1755,7 +1631,6 @@ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" @@ -1860,6 +1735,7 @@ "integrity": "sha512-xRlzazC+QZwr6z4ixEqYHo9fgwhTZ3xNSdljlKfUFGZSdlvt166DljRELFUfFytlYOYvo3vTisA/AFOuOAzFQQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2510,6 +2386,7 @@ "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.1.1" } @@ -2520,6 +2397,7 @@ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2537,14 +2415,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@kwsites/promise-deferred": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@larksuiteoapi/node-sdk": { "version": "1.59.0", @@ -3061,7 +2941,6 @@ "integrity": "sha512-lkg23ge+rgyhgUwXmlbkPEhuhHq/hUi/gXKH+4I7vO+lJrbNfEYcQdJLIGjKyXLQzgFiiyDAwh5vAe/tITAE+w==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "e2e/*" ], @@ -3361,6 +3240,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3379,6 +3259,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3396,6 +3277,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3413,6 +3295,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3430,6 +3313,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3447,6 +3331,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3465,6 +3350,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3482,6 +3368,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3500,6 +3387,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3517,6 +3405,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3534,6 +3423,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3551,6 +3441,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3568,6 +3459,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=20.0.0" } @@ -3578,6 +3470,7 @@ "integrity": "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-app": "^8.1.2", "@octokit/auth-unauthenticated": "^7.0.3", @@ -3597,6 +3490,7 @@ "integrity": "sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-oauth-app": "^9.0.3", "@octokit/auth-oauth-user": "^6.0.2", @@ -3617,6 +3511,7 @@ "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/auth-oauth-user": "^6.0.2", @@ -3634,6 +3529,7 @@ "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/oauth-methods": "^6.0.2", "@octokit/request": "^10.0.6", @@ -3650,6 +3546,7 @@ "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/oauth-methods": "^6.0.2", @@ -3667,6 +3564,7 @@ "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 20" } @@ -3677,6 +3575,7 @@ "integrity": "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0" @@ -3711,6 +3610,7 @@ "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" @@ -3725,6 +3625,7 @@ "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", @@ -3740,6 +3641,7 @@ "integrity": "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-oauth-app": "^9.0.2", "@octokit/auth-oauth-user": "^6.0.1", @@ -3760,6 +3662,7 @@ "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 20" } @@ -3770,6 +3673,7 @@ "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/oauth-authorization-url": "^8.0.0", "@octokit/request": "^10.0.6", @@ -3785,14 +3689,16 @@ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@octokit/openapi-webhooks-types": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz", "integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@octokit/plugin-paginate-graphql": { "version": "6.0.0", @@ -3800,6 +3706,7 @@ "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 20" }, @@ -3813,6 +3720,7 @@ "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^16.0.0" }, @@ -3829,6 +3737,7 @@ "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^16.0.0" }, @@ -3845,6 +3754,7 @@ "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", @@ -3863,6 +3773,7 @@ "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" @@ -3880,6 +3791,7 @@ "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", @@ -3898,6 +3810,7 @@ "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/types": "^16.0.0" }, @@ -3911,6 +3824,7 @@ "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/openapi-types": "^27.0.0" } @@ -3921,6 +3835,7 @@ "integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/openapi-webhooks-types": "12.1.0", "@octokit/request-error": "^7.0.0", @@ -3936,6 +3851,7 @@ "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 20" } @@ -4039,6 +3955,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10" }, @@ -4066,6 +3983,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4083,6 +4001,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4100,6 +4019,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4117,6 +4037,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4134,6 +4055,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4151,6 +4073,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4168,6 +4091,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10" } @@ -4185,6 +4109,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10" } @@ -5614,6 +5539,7 @@ "integrity": "sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12.17.0" }, @@ -5695,7 +5621,8 @@ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", "integrity": "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/body-parser": { "version": "1.19.6", @@ -5742,7 +5669,6 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -5980,14 +5906,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -6065,6 +5983,7 @@ "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.16" }, @@ -6103,46 +6022,6 @@ "dev": true, "license": "MIT" }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6192,6 +6071,7 @@ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "retry": "0.13.1" } @@ -6271,7 +6151,8 @@ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/bignumber.js": { "version": "9.3.1", @@ -6446,7 +6327,8 @@ "resolved": "https://registry.npmjs.org/chmodrp/-/chmodrp-1.0.2.tgz", "integrity": "sha512-TdngOlFV1FLTzU0o1w8MB6/BFywhtLC0SzRTGJU7T9lmdjlCWeMRt1iVo0Ki+ldwNk0BqNiKoc8xpLZEQ8mY1w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/chokidar": { "version": "5.0.0", @@ -6486,6 +6368,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6496,6 +6379,7 @@ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "restore-cursor": "^5.0.0" }, @@ -6592,6 +6476,7 @@ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -6619,6 +6504,7 @@ "integrity": "sha512-YbUP88RDwCvoQkZhRtGURYm9RIpWdtvZuhT87fKNoLjk8kIFIFeARpKfuZQGdwfH99GZpUmqSfcDrK62X7lTgg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.3", "fs-extra": "^11.3.3", @@ -6643,6 +6529,7 @@ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -6660,7 +6547,8 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/color-convert": { "version": "2.0.1", @@ -6680,17 +6568,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6710,6 +6587,7 @@ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14" } @@ -6724,22 +6602,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -6917,6 +6779,7 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4.0.0" } @@ -6946,14 +6809,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7179,6 +7034,7 @@ "integrity": "sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -7373,7 +7229,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7483,7 +7338,8 @@ "url": "https://opencollective.com/fastify" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -7608,6 +7464,7 @@ "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -7621,6 +7478,7 @@ "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "filename-reserved-regex": "^3.0.0" }, @@ -7772,6 +7630,7 @@ "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -7781,50 +7640,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -7849,29 +7664,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/gaxios": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", @@ -8226,7 +8018,6 @@ "integrity": "sha512-wcHAQ1e7svL3fJMpDchcQVcWUmywhuepOOjHUHmMmWAwUJEIyK5ea5sbSjZd+Gy1aMpZeP8VYJa+4tP+j1YptQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@grammyjs/types": "3.25.0", "abort-controller": "^3.0.0", @@ -8300,14 +8091,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/hashery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.0.tgz", @@ -8343,6 +8126,18 @@ "node": "*" } }, + "node_modules/hono": { + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", + "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hookified": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", @@ -8551,19 +8346,6 @@ "dev": true, "license": "MIT" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -8575,7 +8357,8 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/ip-address": { "version": "10.1.0", @@ -8602,6 +8385,7 @@ "integrity": "sha512-5w/yZB5lXmTfsvNawmvkCjYo4SJNuKQz/av8TC1UiOyfOHyaM+DReqbpU2XpWYfmY+NIUbRRH8PUAWsxaS+IfA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@tinyhttp/content-disposition": "^2.2.0", "async-retry": "^1.3.3", @@ -8643,6 +8427,7 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8656,6 +8441,7 @@ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8669,6 +8455,7 @@ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-east-asian-width": "^1.3.1" }, @@ -8684,7 +8471,8 @@ "resolved": "https://registry.npmjs.org/lifecycle-utils/-/lifecycle-utils-2.1.0.tgz", "integrity": "sha512-AnrXnE2/OF9PHCyFg0RSqsnQTzV991XaZA/buhFDoc58xU7rhSCDgCz/09Lqpsn4MpoPHt7TRAXV1kWZypFVsA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ipull/node_modules/parse-ms": { "version": "3.0.0", @@ -8692,6 +8480,7 @@ "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8705,6 +8494,7 @@ "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "parse-ms": "^3.0.0" }, @@ -8721,6 +8511,7 @@ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -8738,6 +8529,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -8770,6 +8562,7 @@ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8803,6 +8596,7 @@ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -8823,6 +8617,7 @@ "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=20" } @@ -8889,7 +8684,8 @@ "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.7.tgz", "integrity": "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -8910,6 +8706,7 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -8989,7 +8786,6 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -9072,7 +8868,8 @@ "resolved": "https://registry.npmjs.org/lifecycle-utils/-/lifecycle-utils-3.1.1.tgz", "integrity": "sha512-gNd3OvhFNjHykJE3uGntz7UuPzWlK9phrIdXxU9Adis0+ExkwnZibfxCJWiWWZ+a6VbKiZrb+9D9hCQWd4vjTg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/linkedom": { "version": "0.18.12", @@ -9120,7 +8917,8 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.identity": { "version": "3.0.0", @@ -9198,6 +8996,7 @@ "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" @@ -9221,6 +9020,7 @@ "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "steno": "^4.0.2" }, @@ -9241,34 +9041,6 @@ "node": "20 || >=22" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", @@ -9382,6 +9154,7 @@ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -9411,6 +9184,7 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9438,20 +9212,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9562,6 +9322,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.js" }, @@ -9594,6 +9355,7 @@ "integrity": "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^18 || ^20 || >= 21" } @@ -9603,7 +9365,8 @@ "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.8.0.tgz", "integrity": "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-domexception": { "name": "@nolyfill/domexception", @@ -9659,6 +9422,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@huggingface/jinja": "^0.5.5", "async-retry": "^1.3.3", @@ -9731,6 +9495,7 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9744,6 +9509,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -9762,38 +9528,6 @@ "license": "MIT", "optional": true }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -9835,6 +9569,7 @@ "integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/app": "^16.1.2", "@octokit/core": "^7.0.6", @@ -9890,6 +9625,7 @@ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mimic-function": "^5.0.0" }, @@ -10400,6 +10136,7 @@ "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", @@ -10423,6 +10160,7 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10436,6 +10174,7 @@ "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18.20" }, @@ -10449,6 +10188,7 @@ "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" @@ -10466,6 +10206,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -10626,6 +10367,7 @@ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -10673,17 +10415,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -10797,6 +10528,7 @@ "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.13.1 || >=16.0.0" }, @@ -10810,6 +10542,7 @@ "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "parse-ms": "^4.0.0" }, @@ -11060,6 +10793,7 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -11152,6 +10886,7 @@ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -11169,6 +10904,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, @@ -11186,81 +10922,6 @@ "node": ">= 4" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -11408,14 +11069,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -11436,7 +11089,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", @@ -11583,6 +11235,7 @@ "integrity": "sha512-56a5oxFdWlsGygOXHWrG+xjj5w9ZIt2uQbzqiIGdR/6i5iococ7WQ/bNPzWxCJdEUGUCmyMH0t9zMpRJTaKxmw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -11599,6 +11252,7 @@ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -11616,7 +11270,8 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/sisteransi": { "version": "1.0.5", @@ -11630,7 +11285,8 @@ "resolved": "https://registry.npmjs.org/sleep-promise/-/sleep-promise-9.1.0.tgz", "integrity": "sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/slice-ansi": { "version": "8.0.0", @@ -11638,6 +11294,7 @@ "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" @@ -11655,6 +11312,7 @@ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11668,6 +11326,7 @@ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-east-asian-width": "^1.3.1" }, @@ -11891,6 +11550,7 @@ "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11904,6 +11564,7 @@ "integrity": "sha512-wiS21Jthlvl1to+oorePvcyrIkiG/6M3D3VTmDUlJm7Cy6SbFhKkAvX+YBuHLxck/tO3mrdpC/cNesigQc3+UQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-escapes": "^6.2.0", "ansi-styles": "^6.2.1", @@ -11920,6 +11581,7 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11933,6 +11595,7 @@ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11945,7 +11608,8 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/stdout-update/node_modules/string-width": { "version": "7.2.0", @@ -11953,6 +11617,7 @@ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -11971,6 +11636,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -11987,6 +11653,7 @@ "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -12073,6 +11740,7 @@ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12210,6 +11878,7 @@ "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -12266,6 +11935,7 @@ "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" } @@ -12381,7 +12051,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12438,14 +12107,16 @@ "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/universalify": { "version": "2.0.1", @@ -12453,6 +12124,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -12471,7 +12143,8 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -12508,6 +12181,7 @@ "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^20.17.0 || >=22.9.0" } @@ -12555,6 +12229,7 @@ "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^4.0.0" }, @@ -12565,17 +12240,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/win-guid": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", @@ -12627,10 +12291,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "dev": true, + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -12740,7 +12403,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 54a4d89..b1f33dd 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "@grpc/grpc-js": "^1.14.3", "express": "^4.21.2", "multicast-dns": "^7.2.5", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "ws": "^8.20.0" }, "openclaw": { "extensions": [ diff --git a/src/executor.ts b/src/executor.ts index 5ef32a0..be19740 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -499,7 +499,7 @@ function getOrCreateDeviceIdentity(): { publicKey: string; privateKey: crypto.Ke return cachedDeviceIdentity; } -class GatewayRpcConnection { +export class GatewayRpcConnection { private readonly wsUrl: string; private readonly gatewayToken: string; private readonly gatewayPassword: string; @@ -760,6 +760,11 @@ class GatewayRpcConnection { this.rejectAllPending(error); } + /** Whether the underlying WebSocket socket is currently open (readyState === OPEN). */ + get isOpen(): boolean { + return this.socket?.readyState === 1; + } + private awaitConnectChallenge(): Promise { if (this.connectChallengeRejecter) { this.connectChallengeRejecter(new Error("gateway connect challenge wait superseded")); @@ -963,6 +968,261 @@ class GatewayRpcConnection { } } +// --------------------------------------------------------------------------- +// WebSocket connection pool — reuses GatewayRpcConnection across dispatches +// --------------------------------------------------------------------------- + +export interface WsPoolConfig { + /** Maximum idle time (ms) before a pooled connection is evicted. Default: 300_000 (5 min) */ + idleTimeoutMs?: number; + /** Interval (ms) between heartbeat pings on an idle connection. Default: 60_000 (1 min) */ + heartbeatIntervalMs?: number; + /** Maximum number of reconnection attempts before giving up on a connection. Default: 2 */ + maxReconnectAttempts?: number; +} + +export interface WsPoolStats { + connections: number; + idleConnections: number; + activeConnections: number; + hitRate: number; + totalAcquires: number; + totalReuses: number; + totalReconnects: number; +} + +interface PooledConnection { + conn: GatewayRpcConnection; + key: string; + lastUsedAt: number; + /** Reference count: how many concurrent acquire() calls are using this connection. + * When refCount > 0 the connection is active; when refCount === 0 it is idle. */ + refCount: number; + heartbeatTimer: ReturnType | null; + evictionTimer: ReturnType | null; +} + +/** + * Pool that shares GatewayRpcConnection instances keyed by (wsUrl + token + password). + * + * Without pooling, every `dispatchViaGatewayRpc()` call creates a fresh WebSocket + * connection: TCP handshake + WS upgrade + Ed25519 challenge + `connect` RPC ≈ 100–200 ms + * of pure overhead on localhost, much more over the network. By reusing a persistent + * connection, subsequent dispatches skip all of that and send the `agent` RPC frame + * directly on the existing socket. + * + * The pool also provides: + * - **Heartbeat** — periodic `echo` RPC to keep the connection alive during idle periods. + * - **Auto-reconnect** — transparently recreates a dropped connection before the next dispatch. + * - **Idle eviction** — gracefully closes connections that have been idle longer than + * `idleTimeoutMs`, freeing resources on both the client and the gateway. + */ +export class GatewayRpcConnectionPool { + private readonly pool = new Map(); + private readonly idleTimeoutMs: number; + private readonly heartbeatIntervalMs: number; + private readonly maxReconnectAttempts: number; + private destroyed = false; + + private totalAcquires = 0; + private totalReuses = 0; + private totalReconnects = 0; + + constructor(config: WsPoolConfig = {}) { + this.idleTimeoutMs = config.idleTimeoutMs ?? 300_000; + this.heartbeatIntervalMs = config.heartbeatIntervalMs ?? 60_000; + this.maxReconnectAttempts = config.maxReconnectAttempts ?? 2; + } + + /** + * Acquire a connected GatewayRpcConnection for the given config. + * + * - If a healthy pooled connection exists for the key, it is returned immediately (hot path). + * - If the pooled connection is dead, it is evicted and a fresh one is created + connected. + * - If no pooled connection exists, a new one is created + connected. + * + * Callers MUST call `release()` when done so the connection returns to the pool. + */ + async acquire(config: GatewayRuntimeConfig): Promise { + if (this.destroyed) { + throw new Error("GatewayRpcConnectionPool has been destroyed"); + } + + const key = this.buildKey(config); + this.totalAcquires++; + + const existing = this.pool.get(key); + + // Hot path: reuse healthy connection + if (existing && existing.conn && existing.conn.isOpen) { + existing.refCount++; + existing.lastUsedAt = Date.now(); + this.stopHeartbeat(existing); + // Eviction timer is already stopped while refCount > 0 (set in release), + // but we reset it here for housekeeping so the clock starts fresh on next release. + this.totalReuses++; + return existing.conn; + } + + // Cold path: evict dead connection, create fresh one + if (existing) { + this.evictConnection(existing); + } + + const conn = await this.createAndConnect(config, 0); + const entry: PooledConnection = { + conn, + key, + lastUsedAt: Date.now(), + refCount: 1, + heartbeatTimer: null, + evictionTimer: null, + }; + this.pool.set(key, entry); + return conn; + } + + /** + * Return a connection to the pool. Decrements the reference count; only + * when all acquire() calls have been released (refCount === 0) does the + * connection transition to idle state with heartbeat + eviction timer. + */ + release(config: GatewayRuntimeConfig): void { + if (this.destroyed) return; + + const key = this.buildKey(config); + const entry = this.pool.get(key); + if (!entry) return; + + entry.refCount = Math.max(0, entry.refCount - 1); + entry.lastUsedAt = Date.now(); + + if (entry.refCount === 0) { + // Connection is now idle — start keep-alive and eviction timer + this.startHeartbeat(entry); + this.resetEviction(entry); + } + } + + getStats(): WsPoolStats { + let idle = 0; + let active = 0; + for (const entry of this.pool.values()) { + if (entry.refCount > 0) active++; + else idle++; + } + return { + connections: this.pool.size, + idleConnections: idle, + activeConnections: active, + hitRate: this.totalAcquires > 0 ? this.totalReuses / this.totalAcquires : 0, + totalAcquires: this.totalAcquires, + totalReuses: this.totalReuses, + totalReconnects: this.totalReconnects, + }; + } + + destroy(): void { + if (this.destroyed) return; + this.destroyed = true; + + for (const entry of this.pool.values()) { + this.stopHeartbeat(entry); + this.stopEviction(entry); + try { + entry.conn.close(); + } catch { + // best-effort close during teardown + } + } + this.pool.clear(); + } + + // -- Internal helpers ------------------------------------------------------- + + private buildKey(config: GatewayRuntimeConfig): string { + const h = crypto.createHash("sha256"); + h.update(config.wsUrl + "\0" + config.gatewayToken + "\0" + config.gatewayPassword); + return h.digest("hex"); + } + + private async createAndConnect(config: GatewayRuntimeConfig, attempt: number): Promise { + const conn = new GatewayRpcConnection(config); + try { + await conn.connect(); + return conn; + } catch (error) { + if (attempt < this.maxReconnectAttempts) { + this.totalReconnects++; + // Exponential backoff: 100ms, 200ms, 400ms… + const delay = 100 * Math.pow(2, attempt); + await new Promise((r) => setTimeout(r, delay)); + return this.createAndConnect(config, attempt + 1); + } + throw error; + } + } + + private evictConnection(entry: PooledConnection): void { + this.stopHeartbeat(entry); + this.stopEviction(entry); + try { + entry.conn.close(); + } catch { + // best-effort + } + this.pool.delete(entry.key); + } + + private startHeartbeat(entry: PooledConnection): void { + this.stopHeartbeat(entry); + entry.heartbeatTimer = setInterval(() => { + // Send a lightweight ping to keep the connection alive. + // We use the `request` method with a minimal method — if the socket + // is dead, the pending request will timeout and we'll learn about it. + if (!entry.conn.isOpen) { + // Socket is not open — stop heartbeat, let eviction or next acquire handle it + this.stopHeartbeat(entry); + return; + } + try { + // Fire-and-forget heartbeat: we don't await the result. + // A successful ping means the gateway is responsive. + entry.conn.request("echo", { t: Date.now() }, 5_000, false).catch(() => { + // Heartbeat failed — the connection is likely dead. + // The next acquire() will detect and reconnect. + }); + } catch { + // Synchronous send error — socket is dead + this.stopHeartbeat(entry); + } + }, this.heartbeatIntervalMs); + } + + private stopHeartbeat(entry: PooledConnection): void { + if (entry.heartbeatTimer !== null) { + clearInterval(entry.heartbeatTimer); + entry.heartbeatTimer = null; + } + } + + private resetEviction(entry: PooledConnection): void { + this.stopEviction(entry); + entry.evictionTimer = setTimeout(() => { + if (entry.refCount === 0) { + this.evictConnection(entry); + } + }, this.idleTimeoutMs); + } + + private stopEviction(entry: PooledConnection): void { + if (entry.evictionTimer !== null) { + clearTimeout(entry.evictionTimer); + entry.evictionTimer = null; + } + } +} + /** * Bridges A2A inbound messages to OpenClaw agent dispatch. * @@ -976,6 +1236,7 @@ export class OpenClawAgentExecutor implements AgentExecutor { private readonly agentResponseTimeoutMs: number; private readonly security: GatewayConfig["security"]; private readonly taskContextByTaskId: Map; + private readonly wsPool: GatewayRpcConnectionPool; constructor(api: OpenClawPluginApi, config: GatewayConfig) { this.api = api; @@ -989,6 +1250,12 @@ export class OpenClawAgentExecutor implements AgentExecutor { : DEFAULT_AGENT_RESPONSE_TIMEOUT_MS; this.taskContextByTaskId = new Map(); + this.wsPool = new GatewayRpcConnectionPool(); + } + + /** Clean up the WS connection pool. Call this during plugin shutdown. */ + close(): void { + this.wsPool.destroy(); } async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise { @@ -1188,9 +1455,7 @@ export class OpenClawAgentExecutor implements AgentExecutor { ): Promise { const messageText = extractInboundMessageText(userMessage); const gatewayConfig = this.resolveGatewayRuntimeConfig(); - const gateway = new GatewayRpcConnection(gatewayConfig); - - await gateway.connect(); + const gateway = await this.wsPool.acquire(gatewayConfig); try { // Derive a deterministic session key from A2A contextId for: @@ -1241,7 +1506,7 @@ export class OpenClawAgentExecutor implements AgentExecutor { throw new Error("No assistant response text returned by gateway"); } finally { - gateway.close(); + this.wsPool.release(gatewayConfig); } } diff --git a/tests/ws-pool.test.ts b/tests/ws-pool.test.ts new file mode 100644 index 0000000..814bb8e --- /dev/null +++ b/tests/ws-pool.test.ts @@ -0,0 +1,541 @@ +/** + * GatewayRpcConnectionPool — WebSocket connection pooling tests + * + * Validates: reuse, eviction, heartbeat, reconnect, stats, destroy. + * Uses a mock WebSocket server that speaks the gateway challenge/response protocol. + * + * Run: node --import tsx --test tests/ws-pool.test.ts + */ + +import assert from "node:assert/strict"; +import http from "node:http"; +import { WebSocketServer, WebSocket as WsWebSocket } from "ws"; +import { afterEach, beforeEach, describe, it } from "node:test"; + +import { + GatewayRpcConnectionPool, + type WsPoolConfig, +} from "../src/executor.js"; + +// --------------------------------------------------------------------------- +// Mock gateway server — minimal protocol simulation +// --------------------------------------------------------------------------- + +function setupMockGateway(): Promise<{ + port: number; + close: () => Promise; + connectionCount: () => number; + forceCloseNext: () => void; +}> { + let wsServer: WebSocketServer; + let httpServer: http.Server; + let connections = 0; + let forceClose = false; + + return new Promise((resolve) => { + httpServer = http.createServer(); + wsServer = new WebSocketServer({ server: httpServer }); + + wsServer.on("connection", (socket) => { + connections++; + const shouldClose = forceClose; + forceClose = false; + + if (shouldClose) { + socket.close(); + return; + } + + // Send challenge event like real gateway + socket.send(JSON.stringify({ + type: "event", + event: "connect.challenge", + payload: { nonce: "test-nonce-" + Date.now() }, + })); + + socket.on("message", (raw) => { + let frame: any; + try { + frame = JSON.parse(raw.toString()); + } catch { + return; + } + + if (frame.type === "req") { + // Handle connect + if (frame.method === "connect") { + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + scopes: ["operator.admin", "operator.read", "operator.write"], + }, + })); + return; + } + + // Handle echo (heartbeat) + if (frame.method === "echo") { + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { ok: true }, + })); + return; + } + + // Handle agent + if (frame.method === "agent") { + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + status: "accepted", + }, + })); + + // Then send final result + setTimeout(() => { + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + status: "ok", + result: { + payloads: [{ text: "Hello from agent" }], + }, + }, + })); + }, 10); + return; + } + + // Handle chat.history + if (frame.method === "chat.history") { + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + history: [ + { role: "assistant", content: "History reply" }, + ], + }, + })); + return; + } + + // Default: respond ok + socket.send(JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: {}, + })); + } + }); + }); + + httpServer.listen(0, "127.0.0.1", () => { + const port = (httpServer.address() as any).port; + resolve({ + port, + close: () => new Promise((res) => { + wsServer.close(); + httpServer.close(() => res()); + }), + connectionCount: () => connections, + forceCloseNext: () => { forceClose = true; }, + }); + }); + }); +} + +// --------------------------------------------------------------------------- +// Install mock WebSocket on globalThis (GatewayRpcConnection reads it) +// --------------------------------------------------------------------------- + +function installMockWebSocket() { + (globalThis as any).WebSocket = class MockWebSocket { + static OPEN = 1; + static CLOSED = 3; + static CLOSING = 2; + static CONNECTING = 0; + + readyState: number = 0; + private ws: WsWebSocket | null = null; + + constructor(url: string) { + this.connect(url); + } + + private connect(url: string) { + this.ws = new WsWebSocket(url); + this.readyState = 0; + + this.ws.on("open", () => { + this.readyState = 1; + this.dispatchEvent?.({ type: "open" } as any); + }); + + this.ws.on("message", (data: any) => { + this.dispatchEvent?.({ type: "message", data: data.toString() } as any); + }); + + this.ws.on("close", () => { + this.readyState = 3; + this.dispatchEvent?.({ type: "close" } as any); + }); + + this.ws.on("error", () => { + this.readyState = 3; + this.dispatchEvent?.({ type: "error" } as any); + }); + } + + send(data: string) { + this.ws?.send(data); + } + + close() { + this.readyState = 2; + this.ws?.close(); + } + + // EventTarget-like interface + private _listeners: Map = new Map(); + + addEventListener(type: string, listener: Function) { + if (!this._listeners.has(type)) this._listeners.set(type, []); + this._listeners.get(type)!.push(listener); + } + + removeEventListener(type: string, listener: Function) { + const list = this._listeners.get(type); + if (list) { + const idx = list.indexOf(listener); + if (idx >= 0) list.splice(idx, 1); + } + } + + dispatchEvent(event: any): boolean { + const list = this._listeners.get(event.type); + if (list) { + for (const fn of list) fn(event); + } + return true; + } + }; +} + +function uninstallMockWebSocket() { + delete (globalThis as any).WebSocket; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("GatewayRpcConnectionPool", () => { + let server: Awaited>; + + beforeEach(async () => { + server = await setupMockGateway(); + installMockWebSocket(); + }); + + afterEach(async () => { + await server.close(); + uninstallMockWebSocket(); + }); + + function makeConfig() { + return { + port: server.port, + wsUrl: `ws://127.0.0.1:${server.port}`, + hooksWakeUrl: "", + gatewayToken: "test-token", + gatewayPassword: "", + hooksToken: "", + }; + } + + it("creates a new connection on first acquire", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config = makeConfig(); + + const conn = await pool.acquire(config); + assert.ok(conn, "Should return a connection"); + pool.release(config); + + const stats = pool.getStats(); + assert.equal(stats.connections, 1); + assert.equal(stats.totalAcquires, 1); + assert.equal(stats.totalReuses, 0); + + pool.destroy(); + }); + + it("reuses the same connection on second acquire", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config = makeConfig(); + + const conn1 = await pool.acquire(config); + pool.release(config); + + const initialConnections = server.connectionCount(); + + const conn2 = await pool.acquire(config); + pool.release(config); + + // Should reuse the same WS connection - no new connections on server + assert.equal(server.connectionCount(), initialConnections, "No new WS connections created for reuse"); + + const stats = pool.getStats(); + assert.equal(stats.totalAcquires, 2); + assert.equal(stats.totalReuses, 1); + + pool.destroy(); + }); + + it("creates separate connections for different configs", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config1 = makeConfig(); + const config2 = { ...makeConfig(), gatewayToken: "different-token" }; + + const conn1 = await pool.acquire(config1); + pool.release(config1); + + const conn2 = await pool.acquire(config2); + pool.release(config2); + + const stats = pool.getStats(); + assert.equal(stats.connections, 2, "Different keys should have separate connections"); + assert.equal(stats.totalAcquires, 2); + assert.equal(stats.totalReuses, 0); + + pool.destroy(); + }); + + it("tracks hit rate correctly", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config = makeConfig(); + + // First acquire — miss (cold) + await pool.acquire(config); + pool.release(config); + + // Second acquire — hit (reuse) + await pool.acquire(config); + pool.release(config); + + // Third acquire — hit (reuse) + await pool.acquire(config); + pool.release(config); + + const stats = pool.getStats(); + assert.equal(stats.totalAcquires, 3); + assert.equal(stats.totalReuses, 2); + assert.ok(Math.abs(stats.hitRate - 2 / 3) < 0.01, `hitRate should be ~0.667, got ${stats.hitRate}`); + + pool.destroy(); + }); + + it("evicts idle connections after idleTimeoutMs", async () => { + const pool = new GatewayRpcConnectionPool({ + idleTimeoutMs: 200, // very short for test + heartbeatIntervalMs: 999_999, // disable heartbeat for eviction test + }); + const config = makeConfig(); + + await pool.acquire(config); + pool.release(config); + + assert.equal(pool.getStats().connections, 1, "Connection should be in pool after release"); + + // Wait for eviction timer to fire + await new Promise((r) => setTimeout(r, 400)); + + assert.equal(pool.getStats().connections, 0, "Connection should be evicted after idle timeout"); + + pool.destroy(); + }); + + it("resets eviction timer on release", async () => { + const pool = new GatewayRpcConnectionPool({ + idleTimeoutMs: 300, + heartbeatIntervalMs: 999_999, + }); + const config = makeConfig(); + + await pool.acquire(config); + pool.release(config); + + // After 150ms, re-acquire and release (resets eviction timer) + await new Promise((r) => setTimeout(r, 150)); + await pool.acquire(config); + pool.release(config); + + // After another 200ms — would have been evicted if timer wasn't reset + await new Promise((r) => setTimeout(r, 200)); + assert.equal(pool.getStats().connections, 1, "Connection should still be in pool — timer was reset"); + + // Wait for full eviction from the reset point + await new Promise((r) => setTimeout(r, 200)); + assert.equal(pool.getStats().connections, 0, "Connection should be evicted after reset timeout"); + + pool.destroy(); + }); + + it("rejects acquire after destroy", async () => { + const pool = new GatewayRpcConnectionPool(); + pool.destroy(); + + await assert.rejects( + async () => pool.acquire(makeConfig()), + /destroyed/, + ); + }); + + it("destroy closes all connections", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config1 = makeConfig(); + const config2 = { ...makeConfig(), gatewayToken: "other" }; + + await pool.acquire(config1); + pool.release(config1); + await pool.acquire(config2); + pool.release(config2); + + assert.equal(pool.getStats().connections, 2); + + pool.destroy(); + + assert.equal(pool.getStats().connections, 0); + }); + + it("double destroy is safe", () => { + const pool = new GatewayRpcConnectionPool(); + pool.destroy(); + pool.destroy(); // should not throw + }); + + it("reconnects when existing connection is dead", async () => { + const pool = new GatewayRpcConnectionPool({ + idleTimeoutMs: 5_000, + maxReconnectAttempts: 1, + }); + const config = makeConfig(); + + const conn1 = await pool.acquire(config); + pool.release(config); + + const connectionsBefore = server.connectionCount(); + + // Force-close the underlying connection + const socket = (conn1 as any).socket; + if (socket) socket.close(); + + // Wait for close to propagate + await new Promise((r) => setTimeout(r, 50)); + + // Next acquire should detect dead connection and create a new one + const conn2 = await pool.acquire(config); + pool.release(config); + + // Server should have received a new connection + assert.ok(server.connectionCount() > connectionsBefore, "New WS connection created after reconnect"); + + const stats = pool.getStats(); + assert.equal(stats.connections, 1); + + pool.destroy(); + }); + + it("reports correct active/idle counts", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config = makeConfig(); + + // Acquire — active (refCount = 1) + await pool.acquire(config); + let stats = pool.getStats(); + assert.equal(stats.activeConnections, 1); + assert.equal(stats.idleConnections, 0); + + // Release — idle (refCount = 0) + pool.release(config); + stats = pool.getStats(); + assert.equal(stats.activeConnections, 0); + assert.equal(stats.idleConnections, 1); + + pool.destroy(); + }); + + it("concurrent acquire: connection is not evicted while still in use", async () => { + const pool = new GatewayRpcConnectionPool({ + idleTimeoutMs: 200, // short timeout for test + heartbeatIntervalMs: 999_999, + }); + const config = makeConfig(); + + // Two concurrent acquires — both get the same connection + const conn1 = await pool.acquire(config); + const conn2 = await pool.acquire(config); + + // Same underlying connection object + assert.strictEqual(conn1, conn2, "Concurrent acquires should return the same connection"); + + let stats = pool.getStats(); + assert.equal(stats.activeConnections, 1, "One active connection with refCount=2"); + assert.equal(stats.idleConnections, 0); + + // Release one — connection should still be active (refCount = 1) + pool.release(config); + stats = pool.getStats(); + assert.equal(stats.activeConnections, 1, "Still active after partial release"); + assert.equal(stats.idleConnections, 0); + + // Wait past the idle timeout — should NOT be evicted because refCount > 0 + await new Promise((r) => setTimeout(r, 350)); + stats = pool.getStats(); + assert.equal(stats.connections, 1, "Connection not evicted while still in use"); + + // Release the remaining one — now idle (refCount = 0) + pool.release(config); + stats = pool.getStats(); + assert.equal(stats.activeConnections, 0); + assert.equal(stats.idleConnections, 1, "Now idle after final release"); + + // Wait for eviction + await new Promise((r) => setTimeout(r, 350)); + stats = pool.getStats(); + assert.equal(stats.connections, 0, "Connection evicted after becoming idle"); + + pool.destroy(); + }); + + it("reuses connection for many sequential dispatches", async () => { + const pool = new GatewayRpcConnectionPool({ idleTimeoutMs: 5_000 }); + const config = makeConfig(); + + // Simulate 10 sequential dispatches + for (let i = 0; i < 10; i++) { + await pool.acquire(config); + pool.release(config); + } + + // Should have created only 1 connection for the pool + // (1 WS connection on the server side, since we reuse the same one) + const stats = pool.getStats(); + assert.equal(stats.connections, 1, "Pool should have 1 connection"); + assert.equal(stats.totalAcquires, 10); + assert.equal(stats.totalReuses, 9); + assert.ok(stats.hitRate >= 0.89, `hitRate should be ~0.9, got ${stats.hitRate}`); + + pool.destroy(); + }); +}); \ No newline at end of file