Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c956cc039 | |||
| 4fcd3b4547 | |||
| 1bad618b29 | |||
| 5670da320a | |||
| 798b80f2f2 | |||
| 693016d1ab |
Generated
+381
-139
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "paperclip-adapter-opencode-k8s",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "paperclip-adapter-opencode-k8s",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.30",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kubernetes/client-node": "^1.0.0",
|
||||
@@ -15,6 +15,7 @@
|
||||
"devDependencies": {
|
||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
||||
"@types/node": "^24.6.0",
|
||||
"@vitest/coverage-v8": "^4.1.5",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^4.1.4"
|
||||
},
|
||||
@@ -22,10 +23,70 @@
|
||||
"@paperclipai/adapter-utils": ">=2026.415.0-canary.7"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
||||
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
|
||||
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -35,9 +96,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
||||
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -56,6 +117,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -63,6 +134,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsep-plugin/assignment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
|
||||
@@ -112,9 +194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
|
||||
"integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -131,9 +213,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.124.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
|
||||
"integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
|
||||
"version": "0.127.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
||||
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -148,9 +230,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -165,9 +247,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -182,9 +264,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -199,9 +281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -216,9 +298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -233,9 +315,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -253,9 +335,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -273,9 +355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -293,9 +375,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -313,9 +395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -333,9 +415,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -353,9 +435,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -370,9 +452,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -380,18 +462,18 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.9.2",
|
||||
"@emnapi/runtime": "1.9.2",
|
||||
"@napi-rs/wasm-runtime": "^1.1.3"
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -406,9 +488,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -423,9 +505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -506,17 +588,48 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz",
|
||||
"integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"ast-v8-to-istanbul": "^1.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.2.0",
|
||||
"magicast": "^0.5.2",
|
||||
"obug": "^2.1.1",
|
||||
"std-env": "^4.0.0-rc.1",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.1.5",
|
||||
"vitest": "4.1.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz",
|
||||
"integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
|
||||
"integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -525,13 +638,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz",
|
||||
"integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz",
|
||||
"integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.4",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -552,9 +665,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz",
|
||||
"integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz",
|
||||
"integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -565,13 +678,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz",
|
||||
"integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz",
|
||||
"integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.4",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -579,14 +692,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz",
|
||||
"integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz",
|
||||
"integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -595,9 +708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz",
|
||||
"integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz",
|
||||
"integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -605,13 +718,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz",
|
||||
"integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz",
|
||||
"integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -644,6 +757,18 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-v8-to-istanbul": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
|
||||
"integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.31",
|
||||
"estree-walker": "^3.0.3",
|
||||
"js-tokens": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -1050,6 +1175,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -1098,6 +1233,13 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
@@ -1116,6 +1258,45 @@
|
||||
"ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"make-dir": "^4.0.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
|
||||
@@ -1125,6 +1306,13 @@
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
||||
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
@@ -1447,6 +1635,34 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/magicast": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
|
||||
"integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -1591,9 +1807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.9",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
||||
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1636,14 +1852,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.15",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
|
||||
"integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.124.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.15"
|
||||
"@oxc-project/types": "=0.127.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.17"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -1652,21 +1868,34 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.15",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.15",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
@@ -1758,6 +1987,19 @@
|
||||
"text-decoder": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
|
||||
@@ -1881,17 +2123,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz",
|
||||
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
||||
"version": "8.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
||||
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.8",
|
||||
"rolldown": "1.0.0-rc.15",
|
||||
"tinyglobby": "^0.2.15"
|
||||
"postcss": "^8.5.10",
|
||||
"rolldown": "1.0.0-rc.17",
|
||||
"tinyglobby": "^0.2.16"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -1959,19 +2201,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz",
|
||||
"integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz",
|
||||
"integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.4",
|
||||
"@vitest/mocker": "4.1.4",
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"@vitest/runner": "4.1.4",
|
||||
"@vitest/snapshot": "4.1.4",
|
||||
"@vitest/spy": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"@vitest/expect": "4.1.5",
|
||||
"@vitest/mocker": "4.1.5",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/runner": "4.1.5",
|
||||
"@vitest/snapshot": "4.1.5",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -1999,12 +2241,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.4",
|
||||
"@vitest/browser-preview": "4.1.4",
|
||||
"@vitest/browser-webdriverio": "4.1.4",
|
||||
"@vitest/coverage-istanbul": "4.1.4",
|
||||
"@vitest/coverage-v8": "4.1.4",
|
||||
"@vitest/ui": "4.1.4",
|
||||
"@vitest/browser-playwright": "4.1.5",
|
||||
"@vitest/browser-preview": "4.1.5",
|
||||
"@vitest/browser-webdriverio": "4.1.5",
|
||||
"@vitest/coverage-istanbul": "4.1.5",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"@vitest/ui": "4.1.5",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paperclip-adapter-opencode-k8s",
|
||||
"version": "0.1.28",
|
||||
"version": "0.1.31",
|
||||
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
@@ -33,6 +33,7 @@
|
||||
"devDependencies": {
|
||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
||||
"@types/node": "^24.6.0",
|
||||
"@vitest/coverage-v8": "^4.1.5",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^4.1.4"
|
||||
}
|
||||
|
||||
@@ -248,3 +248,181 @@ describe("formatEvent", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
import { parseStdoutLine } from "./format-event.js";
|
||||
|
||||
describe("parseStdoutLine (cli)", () => {
|
||||
const TS = "2026-04-25T22:00:00.000Z";
|
||||
|
||||
it("returns empty for empty input", () => {
|
||||
expect(parseStdoutLine("", TS)).toEqual([]);
|
||||
expect(parseStdoutLine(" ", TS)).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns stdout entry for non-JSON input", () => {
|
||||
expect(parseStdoutLine("plain log", TS)).toEqual([{ kind: "stdout", ts: TS, text: "plain log" }]);
|
||||
});
|
||||
|
||||
it("returns stdout entry when JSON parses to a non-object primitive", () => {
|
||||
expect(parseStdoutLine("42", TS)).toEqual([{ kind: "stdout", ts: TS, text: "42" }]);
|
||||
});
|
||||
|
||||
it("renders a text event as an assistant delta", () => {
|
||||
const line = JSON.stringify({ type: "text", part: { text: "Hello" } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "assistant", ts: TS, text: "Hello", delta: true }]);
|
||||
});
|
||||
|
||||
it("returns empty for text event with empty text", () => {
|
||||
const line = JSON.stringify({ type: "text", part: { text: "" } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([]);
|
||||
});
|
||||
|
||||
it("renders tool_use status=error as tool_result with isError", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "error", error: "boom" } } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([
|
||||
{ kind: "tool_result", ts: TS, toolUseId: "t1", toolName: "bash", content: "boom", isError: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses 'Tool error' fallback when error event has no error string", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "error" } } });
|
||||
const result = parseStdoutLine(line, TS);
|
||||
expect((result[0] as { content: string }).content).toBe("Tool error");
|
||||
});
|
||||
|
||||
it("renders tool_use status=completed as tool_result with output", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "completed", output: "ok" } } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([
|
||||
{ kind: "tool_result", ts: TS, toolUseId: "t1", toolName: "bash", content: "ok", isError: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders tool_use status=done — falls back to description when no output", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "done", description: "did it" } } });
|
||||
expect((parseStdoutLine(line, TS)[0] as { content: string }).content).toBe("did it");
|
||||
});
|
||||
|
||||
it("renders tool_use status=done — falls back to 'Done' when no output or description", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "done" } } });
|
||||
expect((parseStdoutLine(line, TS)[0] as { content: string }).content).toBe("Done");
|
||||
});
|
||||
|
||||
it("renders tool_use pending status as tool_call", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { tool: "bash", id: "t1", state: { status: "running", description: "go" } } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([
|
||||
{ kind: "tool_call", ts: TS, name: "bash", input: "go", toolUseId: "t1" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to part.type then 'tool' when no part.tool name", () => {
|
||||
const line = JSON.stringify({ type: "tool_use", part: { type: "edit", state: { status: "running" } } });
|
||||
expect((parseStdoutLine(line, TS)[0] as { name: string }).name).toBe("edit");
|
||||
const line2 = JSON.stringify({ type: "tool_use", part: { state: { status: "running" } } });
|
||||
expect((parseStdoutLine(line2, TS)[0] as { name: string }).name).toBe("tool");
|
||||
});
|
||||
|
||||
it("renders step_finish with token/cost metrics", () => {
|
||||
const line = JSON.stringify({
|
||||
type: "step_finish",
|
||||
part: {
|
||||
message: "did the thing",
|
||||
reason: "stop",
|
||||
tokens: { input: 100, output: 50, reasoning: 10, cache: { read: 30 } },
|
||||
cost: 0.0123,
|
||||
},
|
||||
});
|
||||
const result = parseStdoutLine(line, TS);
|
||||
expect(result).toEqual([{
|
||||
kind: "result",
|
||||
ts: TS,
|
||||
text: "did the thing",
|
||||
inputTokens: 100,
|
||||
outputTokens: 60,
|
||||
cachedTokens: 30,
|
||||
costUsd: 0.0123,
|
||||
subtype: "stop",
|
||||
isError: false,
|
||||
errors: [],
|
||||
}]);
|
||||
});
|
||||
|
||||
it("renders step_finish with default text when no message", () => {
|
||||
const line = JSON.stringify({ type: "step_finish", part: { reason: "stop" } });
|
||||
expect((parseStdoutLine(line, TS)[0] as { text: string }).text).toBe("Step finished: stop");
|
||||
const line2 = JSON.stringify({ type: "step_finish", part: {} });
|
||||
expect((parseStdoutLine(line2, TS)[0] as { text: string }).text).toBe("Step finished: done");
|
||||
});
|
||||
|
||||
it("renders step_start as a system entry", () => {
|
||||
const line = JSON.stringify({ type: "step_start" });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "system", ts: TS, text: "Starting step…" }]);
|
||||
});
|
||||
|
||||
it("renders assistant event with nested text content", () => {
|
||||
const line = JSON.stringify({
|
||||
type: "assistant",
|
||||
part: { message: { content: [{ type: "text", text: "hi there" }] } },
|
||||
});
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "assistant", ts: TS, text: "hi there" }]);
|
||||
});
|
||||
|
||||
it("handles assistant content as a single non-array object", () => {
|
||||
const line = JSON.stringify({
|
||||
type: "assistant",
|
||||
part: { message: { content: { type: "text", text: "single" } } },
|
||||
});
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "assistant", ts: TS, text: "single" }]);
|
||||
});
|
||||
|
||||
it("returns empty for assistant event with no extractable text", () => {
|
||||
const line = JSON.stringify({ type: "assistant", part: { message: { content: [{ type: "image" }] } } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([]);
|
||||
const line2 = JSON.stringify({ type: "assistant", part: {} });
|
||||
expect(parseStdoutLine(line2, TS)).toEqual([]);
|
||||
});
|
||||
|
||||
it("renders error event with errorText", () => {
|
||||
const line = JSON.stringify({ type: "error", error: { message: "broken" } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "stderr", ts: TS, text: "broken" }]);
|
||||
});
|
||||
|
||||
it("returns empty for error event with empty error string", () => {
|
||||
const line = JSON.stringify({ type: "error", error: "" });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses error.code fallback in errorText", () => {
|
||||
const line = JSON.stringify({ type: "error", error: { code: "E_X" } });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([{ kind: "stderr", ts: TS, text: "E_X" }]);
|
||||
});
|
||||
|
||||
it("uses nested data.message and name fallbacks in errorText", () => {
|
||||
const l1 = JSON.stringify({ type: "error", error: { data: { message: "nested" } } });
|
||||
expect((parseStdoutLine(l1, TS)[0] as { text: string }).text).toBe("nested");
|
||||
const l2 = JSON.stringify({ type: "error", error: { name: "ProviderErr" } });
|
||||
expect((parseStdoutLine(l2, TS)[0] as { text: string }).text).toBe("ProviderErr");
|
||||
});
|
||||
|
||||
it("falls back to JSON.stringify of the error object when nothing else matches", () => {
|
||||
const line = JSON.stringify({ type: "error", error: { weirdKey: "x" } });
|
||||
expect((parseStdoutLine(line, TS)[0] as { text: string }).text).toContain("weirdKey");
|
||||
});
|
||||
|
||||
it("returns empty array for unknown event types", () => {
|
||||
const line = JSON.stringify({ type: "totally_unknown" });
|
||||
expect(parseStdoutLine(line, TS)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatEvent — additional coverage", () => {
|
||||
it("returns empty for safeJsonParse of a non-object primitive", () => {
|
||||
// formatEvent treats a non-object as non-JSON and returns the trimmed line as-is
|
||||
const result = formatEvent("42", false);
|
||||
expect(result).toBe("42");
|
||||
});
|
||||
|
||||
it("returns empty for error event with empty error string", () => {
|
||||
const line = JSON.stringify({ type: "error", error: "" });
|
||||
expect(formatEvent(line, false)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -879,11 +879,16 @@ describe("execute — log dedup (waitForPod status dedup)", () => {
|
||||
describe("execute — external cancel polling", () => {
|
||||
const KEEPALIVE_MS = 15_000;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.PAPERCLIP_DEV_API_KEY = "test-key";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
delete process.env.PAPERCLIP_API_URL;
|
||||
delete process.env.PAPERCLIP_API_KEY;
|
||||
delete process.env.PAPERCLIP_DEV_API_KEY;
|
||||
});
|
||||
|
||||
it("returns errorCode=cancelled and deletes job when issue status is cancelled", async () => {
|
||||
@@ -1212,3 +1217,379 @@ describe("isK8s404", () => {
|
||||
expect(isK8s404(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseModelProvider", () => {
|
||||
it("returns null for null input", async () => {
|
||||
const { parseModelProvider } = await import("./execute.js");
|
||||
expect(parseModelProvider(null)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when model has no slash separator", async () => {
|
||||
const { parseModelProvider } = await import("./execute.js");
|
||||
expect(parseModelProvider("gpt-4")).toBeNull();
|
||||
expect(parseModelProvider(" ")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the provider segment from a slash-separated model id", async () => {
|
||||
const { parseModelProvider } = await import("./execute.js");
|
||||
expect(parseModelProvider("anthropic/claude-opus-4")).toBe("anthropic");
|
||||
expect(parseModelProvider("openai/gpt-4o")).toBe("openai");
|
||||
});
|
||||
|
||||
it("trims whitespace inside the provider segment", async () => {
|
||||
const { parseModelProvider } = await import("./execute.js");
|
||||
expect(parseModelProvider(" bedrock /claude")).toBe("bedrock");
|
||||
});
|
||||
|
||||
it("returns null when provider segment is whitespace only", async () => {
|
||||
const { parseModelProvider } = await import("./execute.js");
|
||||
expect(parseModelProvider(" /model")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("completionWithGrace", () => {
|
||||
it("returns the completion result when it resolves before grace expires", async () => {
|
||||
const { completionWithGrace } = await import("./execute.js");
|
||||
const result = await completionWithGrace(
|
||||
Promise.resolve({ succeeded: true, timedOut: false, jobGone: false }),
|
||||
1000,
|
||||
);
|
||||
expect(result).toEqual({ succeeded: true, timedOut: false, jobGone: false });
|
||||
});
|
||||
|
||||
it("returns timedOut result when grace expires first", async () => {
|
||||
const { completionWithGrace } = await import("./execute.js");
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const slowCompletion = new Promise<{ succeeded: boolean; timedOut: boolean; jobGone: boolean }>(() => {});
|
||||
const racePromise = completionWithGrace(slowCompletion, 50);
|
||||
await vi.advanceTimersByTimeAsync(60);
|
||||
const result = await racePromise;
|
||||
expect(result).toEqual({ succeeded: false, timedOut: true, jobGone: false });
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns timedOut result when completion promise rejects", async () => {
|
||||
const { completionWithGrace } = await import("./execute.js");
|
||||
const result = await completionWithGrace(Promise.reject(new Error("boom")), 1000);
|
||||
expect(result).toEqual({ succeeded: false, timedOut: true, jobGone: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — config edge paths", () => {
|
||||
it("logs a warning but continues when instructionsFilePath cannot be read", async () => {
|
||||
const ctx = makeCtx({ instructionsFilePath: "/does/not/exist/AGENTS.md" });
|
||||
const result = await execute(ctx);
|
||||
expect(result.errorCode).toBeUndefined();
|
||||
const logCalls = vi.mocked(ctx.onLog).mock.calls;
|
||||
const warning = logCalls.find(([_kind, msg]: [string, string]) => typeof msg === "string" && msg.includes("instructionsFilePath not readable"));
|
||||
expect(warning).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns k8s_job_create_failed when ensureAgentDbPvc throws (PVC create rejected)", async () => {
|
||||
vi.mocked(getPvc).mockResolvedValueOnce(null);
|
||||
vi.mocked(createPvc).mockRejectedValueOnce(new Error("storage class missing"));
|
||||
const ctx = makeCtx({
|
||||
agentDbMode: "dedicated_pvc",
|
||||
agentDbStorageClass: "fast",
|
||||
});
|
||||
const result = await execute(ctx);
|
||||
expect(result.errorCode).toBe("k8s_job_create_failed");
|
||||
expect(result.errorMessage).toContain("storage class missing");
|
||||
});
|
||||
|
||||
it("returns k8s_job_create_failed when ensureAgentDbPvc throws because storage class is missing", async () => {
|
||||
vi.mocked(getPvc).mockResolvedValueOnce(null);
|
||||
const ctx = makeCtx({ agentDbMode: "dedicated_pvc" });
|
||||
const result = await execute(ctx);
|
||||
expect(result.errorCode).toBe("k8s_job_create_failed");
|
||||
expect(result.errorMessage).toContain("agentDbStorageClass is required");
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — large-prompt Secret create failure", () => {
|
||||
const LARGE_PROMPT = "y".repeat(300 * 1024);
|
||||
|
||||
it("returns k8s_job_create_failed when createNamespacedSecret throws", async () => {
|
||||
vi.mocked(buildJobManifest).mockReturnValue({
|
||||
job: MOCK_JOB as ReturnType<typeof buildJobManifest>["job"],
|
||||
jobName: JOB_NAME,
|
||||
namespace: NAMESPACE,
|
||||
prompt: LARGE_PROMPT,
|
||||
opencodeArgs: [],
|
||||
promptMetrics: null,
|
||||
} as unknown as ReturnType<typeof buildJobManifest>);
|
||||
|
||||
const coreApi = makeCoreApi();
|
||||
coreApi.createNamespacedSecret.mockRejectedValue(new Error("etcd full"));
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
|
||||
const ctx = makeCtx();
|
||||
const result = await execute(ctx);
|
||||
|
||||
expect(result.errorCode).toBe("k8s_job_create_failed");
|
||||
expect(result.errorMessage).toContain("Failed to create prompt Secret");
|
||||
expect(result.errorMessage).toContain("etcd full");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureAgentDbPvc — verification failure (FAR-85 belt-and-suspenders)", () => {
|
||||
it("throws when getPvc returns null after createPvc resolved (verification failed)", async () => {
|
||||
vi.mocked(getPvc)
|
||||
.mockResolvedValueOnce(null) // first existence check: not found
|
||||
.mockResolvedValueOnce(null); // post-create verification: still not found
|
||||
vi.mocked(createPvc).mockResolvedValueOnce({} as never);
|
||||
await expect(
|
||||
ensureAgentDbPvc("agent-x", "ns-x", { agentDbMode: "dedicated_pvc", agentDbStorageClass: "fast" }),
|
||||
).rejects.toThrow(/PVC opencode-db-agent-x was not created/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — step limit detection", () => {
|
||||
it("logs that the step limit was reached when a step_finish event has reason=max_steps", async () => {
|
||||
const STEP_LIMIT_JSONL = [
|
||||
JSON.stringify({ type: "text", part: { text: "partial" }, sessionID: "ses_step" }),
|
||||
JSON.stringify({ type: "step_finish", part: { reason: "max_steps", tokens: { input: 10, output: 5 }, cost: 0 } }),
|
||||
].join("\n");
|
||||
|
||||
const coreApi = makeCoreApi(STEP_LIMIT_JSONL, 0);
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
|
||||
const ctx = makeCtx();
|
||||
await execute(ctx);
|
||||
|
||||
const logCalls = vi.mocked(ctx.onLog).mock.calls;
|
||||
const limitLog = logCalls.find(
|
||||
([_kind, msg]: [string, string]) => typeof msg === "string" && msg.includes("step limit reached"),
|
||||
);
|
||||
expect(limitLog).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — waitForPod 'no pod yet' messaging", () => {
|
||||
it("emits a 'Waiting for Job controller to create pod' log when pod is not yet present", async () => {
|
||||
const coreApi = makeCoreApi();
|
||||
// First listNamespacedPod call returns empty (no pod yet), second returns Running
|
||||
coreApi.listNamespacedPod = vi.fn()
|
||||
.mockResolvedValueOnce({ items: [] })
|
||||
.mockResolvedValueOnce({
|
||||
items: [{ metadata: { name: POD_NAME }, status: { phase: "Running" } }],
|
||||
})
|
||||
.mockResolvedValue({
|
||||
items: [{ status: { containerStatuses: [{ name: "opencode", state: { terminated: { exitCode: 0 } } }] } }],
|
||||
});
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
|
||||
const ctx = makeCtx();
|
||||
await execute(ctx);
|
||||
|
||||
const logCalls = vi.mocked(ctx.onLog).mock.calls;
|
||||
const waitLog = logCalls.find(
|
||||
([_kind, msg]: [string, string]) => typeof msg === "string" && msg.includes("Waiting for Job controller to create pod"),
|
||||
);
|
||||
expect(waitLog).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — pod scheduling failure (extra paths)", () => {
|
||||
it("returns k8s_pod_schedule_failed when init container is in ImagePullBackOff", async () => {
|
||||
const coreApi = {
|
||||
listNamespacedPod: vi.fn().mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: POD_NAME },
|
||||
status: {
|
||||
phase: "Pending",
|
||||
initContainerStatuses: [
|
||||
{ name: "write-prompt", state: { waiting: { reason: "ImagePullBackOff", message: "back-off" } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
readNamespacedPodLog: vi.fn().mockResolvedValue(""),
|
||||
};
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
const result = await execute(makeCtx());
|
||||
expect(result.errorCode).toBe("k8s_pod_schedule_failed");
|
||||
expect(result.errorMessage).toMatch(/Init container.*image pull failed/);
|
||||
});
|
||||
|
||||
it("returns k8s_pod_schedule_failed when init container is in CrashLoopBackOff", async () => {
|
||||
const coreApi = {
|
||||
listNamespacedPod: vi.fn().mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: POD_NAME },
|
||||
status: {
|
||||
phase: "Pending",
|
||||
initContainerStatuses: [
|
||||
{ name: "write-prompt", state: { waiting: { reason: "CrashLoopBackOff", message: "loop" } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
readNamespacedPodLog: vi.fn().mockResolvedValue(""),
|
||||
};
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
const result = await execute(makeCtx());
|
||||
expect(result.errorCode).toBe("k8s_pod_schedule_failed");
|
||||
expect(result.errorMessage).toMatch(/Init container.*crash loop/);
|
||||
});
|
||||
|
||||
it("returns k8s_pod_schedule_failed when main container is in CrashLoopBackOff", async () => {
|
||||
const coreApi = {
|
||||
listNamespacedPod: vi.fn().mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: POD_NAME },
|
||||
status: {
|
||||
phase: "Pending",
|
||||
containerStatuses: [
|
||||
{ name: "opencode", state: { waiting: { reason: "CrashLoopBackOff", message: "loop" } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
readNamespacedPodLog: vi.fn().mockResolvedValue(""),
|
||||
};
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
const result = await execute(makeCtx());
|
||||
expect(result.errorCode).toBe("k8s_pod_schedule_failed");
|
||||
expect(result.errorMessage).toMatch(/crash loop/);
|
||||
});
|
||||
|
||||
it("proceeds when all init containers terminated successfully and main is running", async () => {
|
||||
const coreApi = {
|
||||
listNamespacedPod: vi.fn()
|
||||
.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: POD_NAME },
|
||||
status: {
|
||||
phase: "Pending",
|
||||
initContainerStatuses: [
|
||||
{ name: "write-prompt", state: { terminated: { exitCode: 0 } } },
|
||||
],
|
||||
containerStatuses: [{ name: "opencode", state: { running: {} } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.mockResolvedValue({
|
||||
items: [{ status: { containerStatuses: [{ name: "opencode", state: { terminated: { exitCode: 0 } } }] } }],
|
||||
}),
|
||||
readNamespacedPodLog: vi.fn().mockResolvedValue(HAPPY_JSONL),
|
||||
};
|
||||
vi.mocked(getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof getCoreApi>);
|
||||
const result = await execute(makeCtx());
|
||||
expect(result.errorCode).toBeUndefined();
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — skill bundle source loading", () => {
|
||||
it("reads SKILL.md from entry.source dir and bundles content into the prompt", async () => {
|
||||
const { mkdtempSync, writeFileSync, mkdirSync } = await import("node:fs");
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "skills-test-"));
|
||||
const skillDir = path.join(tmpDir, "skill-a");
|
||||
mkdirSync(skillDir);
|
||||
writeFileSync(path.join(skillDir, "SKILL.md"), "skill A content");
|
||||
|
||||
const utils = await import("@paperclipai/adapter-utils/server-utils");
|
||||
vi.mocked(utils.readPaperclipRuntimeSkillEntries).mockResolvedValueOnce([
|
||||
{ key: "paperclip/skill-a", runtimeName: "skill-a", source: skillDir, required: true } as never,
|
||||
]);
|
||||
|
||||
const ctx = makeCtx();
|
||||
await execute(ctx);
|
||||
|
||||
// buildJobManifest should have received the skills bundle content
|
||||
const buildArgs = vi.mocked(buildJobManifest).mock.calls[0][0];
|
||||
expect(buildArgs.skillsBundleContent).toContain("skill A content");
|
||||
});
|
||||
|
||||
it("falls back to reading entry.source as a file when SKILL.md path read throws", async () => {
|
||||
const { mkdtempSync, writeFileSync } = await import("node:fs");
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "skills-flat-"));
|
||||
const skillFile = path.join(tmpDir, "skill-b.md");
|
||||
writeFileSync(skillFile, "skill B flat content");
|
||||
|
||||
const utils = await import("@paperclipai/adapter-utils/server-utils");
|
||||
vi.mocked(utils.readPaperclipRuntimeSkillEntries).mockResolvedValueOnce([
|
||||
{ key: "paperclip/skill-b", runtimeName: "skill-b", source: skillFile, required: true } as never,
|
||||
]);
|
||||
|
||||
const ctx = makeCtx();
|
||||
await execute(ctx);
|
||||
|
||||
const buildArgs = vi.mocked(buildJobManifest).mock.calls[0][0];
|
||||
expect(buildArgs.skillsBundleContent).toContain("skill B flat content");
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — SIGTERM handler body (FAR-86 coverage)", () => {
|
||||
it("invoking the captured SIGTERM handler deletes tracked Jobs and Secrets", async () => {
|
||||
// Force a fresh module so sigtermHandlerInstalled starts false again.
|
||||
vi.resetModules();
|
||||
vi.doMock("./k8s-client.js", () => ({
|
||||
getSelfPodInfo: vi.fn().mockResolvedValue(MOCK_SELF_POD),
|
||||
getBatchApi: vi.fn(),
|
||||
getCoreApi: vi.fn(),
|
||||
getLogApi: vi.fn(),
|
||||
getPvc: vi.fn().mockResolvedValue({ metadata: { name: "opencode-db-x" } }),
|
||||
createPvc: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
vi.doMock("./job-manifest.js", () => ({
|
||||
buildJobManifest: vi.fn().mockReturnValue({
|
||||
job: MOCK_JOB,
|
||||
jobName: "fresh-job",
|
||||
namespace: NAMESPACE,
|
||||
prompt: "p",
|
||||
opencodeArgs: [],
|
||||
promptMetrics: null,
|
||||
}),
|
||||
LARGE_PROMPT_THRESHOLD_BYTES: 256 * 1024,
|
||||
}));
|
||||
|
||||
const fresh = await import("./execute.js");
|
||||
const k8s = await import("./k8s-client.js");
|
||||
const batchApi = makeBatchApi();
|
||||
const coreApi = makeCoreApi();
|
||||
const logApi = makeLogApi();
|
||||
vi.mocked(k8s.getBatchApi).mockReturnValue(batchApi as unknown as ReturnType<typeof k8s.getBatchApi>);
|
||||
vi.mocked(k8s.getCoreApi).mockReturnValue(coreApi as unknown as ReturnType<typeof k8s.getCoreApi>);
|
||||
vi.mocked(k8s.getLogApi).mockReturnValue(logApi as unknown as ReturnType<typeof k8s.getLogApi>);
|
||||
|
||||
let capturedHandler: (() => void) | null = null;
|
||||
const onceSpy = vi.spyOn(process, "once").mockImplementation(
|
||||
(event: string | symbol, handler: (...args: unknown[]) => void) => {
|
||||
if (event === "SIGTERM") capturedHandler = handler as () => void;
|
||||
return process;
|
||||
},
|
||||
);
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as never);
|
||||
|
||||
await fresh.execute(makeCtx());
|
||||
onceSpy.mockRestore();
|
||||
|
||||
expect(capturedHandler).not.toBeNull();
|
||||
(capturedHandler as unknown as () => void)();
|
||||
// Wait long enough for the async handler body to settle
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
expect(batchApi.deleteNamespacedJob).toHaveBeenCalled();
|
||||
expect(exitSpy).toHaveBeenCalled();
|
||||
|
||||
exitSpy.mockRestore();
|
||||
vi.doUnmock("./k8s-client.js");
|
||||
vi.doUnmock("./job-manifest.js");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ export function isK8s404(err: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseModelProvider(model: string | null): string | null {
|
||||
export function parseModelProvider(model: string | null): string | null {
|
||||
if (!model) return null;
|
||||
const trimmed = model.trim();
|
||||
if (!trimmed.includes("/")) return null;
|
||||
|
||||
@@ -287,16 +287,16 @@ describe("buildJobManifest", () => {
|
||||
});
|
||||
|
||||
describe("agentDbClaimName — OPENCODE_DB env var", () => {
|
||||
it("sets OPENCODE_DB to /opencode-db when agentDbClaimName is a string (dedicated PVC)", () => {
|
||||
it("sets OPENCODE_DB to /opencode-db/opencode.db when agentDbClaimName is a string (dedicated PVC)", () => {
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod, agentDbClaimName: "opencode-db-agent-abc" });
|
||||
const env = result.job.spec?.template?.spec?.containers?.[0].env ?? [];
|
||||
expect(env.find((e) => e.name === "OPENCODE_DB")?.value).toBe("/opencode-db");
|
||||
expect(env.find((e) => e.name === "OPENCODE_DB")?.value).toBe("/opencode-db/opencode.db");
|
||||
});
|
||||
|
||||
it("sets OPENCODE_DB to /opencode-db when agentDbClaimName is null (ephemeral)", () => {
|
||||
it("sets OPENCODE_DB to /opencode-db/opencode.db when agentDbClaimName is null (ephemeral)", () => {
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod, agentDbClaimName: null });
|
||||
const env = result.job.spec?.template?.spec?.containers?.[0].env ?? [];
|
||||
expect(env.find((e) => e.name === "OPENCODE_DB")?.value).toBe("/opencode-db");
|
||||
expect(env.find((e) => e.name === "OPENCODE_DB")?.value).toBe("/opencode-db/opencode.db");
|
||||
});
|
||||
|
||||
it("does not set OPENCODE_DB when agentDbClaimName is undefined", () => {
|
||||
@@ -305,13 +305,13 @@ describe("agentDbClaimName — OPENCODE_DB env var", () => {
|
||||
expect(env.find((e) => e.name === "OPENCODE_DB")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("replaces a user-provided OPENCODE_DB env override with /opencode-db", () => {
|
||||
it("replaces a user-provided OPENCODE_DB env override with /opencode-db/opencode.db", () => {
|
||||
const selfPod = { ...mockSelfPod, inheritedEnv: { OPENCODE_DB: "/user/override" } };
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod, agentDbClaimName: "opencode-db-agent-abc" });
|
||||
const env = result.job.spec?.template?.spec?.containers?.[0].env ?? [];
|
||||
const dbEntries = env.filter((e) => e.name === "OPENCODE_DB");
|
||||
expect(dbEntries).toHaveLength(1);
|
||||
expect(dbEntries[0].value).toBe("/opencode-db");
|
||||
expect(dbEntries[0].value).toBe("/opencode-db/opencode.db");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -406,3 +406,110 @@ describe("sanitizeLabelValue", () => {
|
||||
expect(warned.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildJobManifest — env wiring branches", () => {
|
||||
it("sets PAPERCLIP_WAKE_PAYLOAD_JSON when paperclipWake is provided", () => {
|
||||
const ctx = { ...mockCtx, context: { ...mockCtx.context, paperclipWake: { reason: "issue_assigned", issue: { id: "x" } } } };
|
||||
const result = buildJobManifest({ ctx, selfPod: mockSelfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_WAKE_PAYLOAD_JSON")?.value).toBeTruthy();
|
||||
});
|
||||
|
||||
it("forwards workspace context and AGENT_HOME from paperclipWorkspace", () => {
|
||||
const ctx = {
|
||||
...mockCtx,
|
||||
context: {
|
||||
...mockCtx.context,
|
||||
paperclipWorkspace: {
|
||||
cwd: "/work",
|
||||
source: "main",
|
||||
strategy: "shared",
|
||||
workspaceId: "ws_1",
|
||||
repoUrl: "https://example.com/r.git",
|
||||
repoRef: "main",
|
||||
branchName: "feature/x",
|
||||
worktreePath: "/wt/x",
|
||||
agentHome: "/home/agent",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = buildJobManifest({ ctx, selfPod: mockSelfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_WORKSPACE_CWD")?.value).toBe("/work");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_WORKSPACE_BRANCH")?.value).toBe("feature/x");
|
||||
expect(env.find((e) => e.name === "AGENT_HOME")?.value).toBe("/home/agent");
|
||||
});
|
||||
|
||||
it("sets PAPERCLIP_LINKED_ISSUE_IDS from non-empty issueIds array (skipping blanks)", () => {
|
||||
const ctx = { ...mockCtx, context: { ...mockCtx.context, issueIds: ["a", " ", "b", null as unknown as string, "c"] } };
|
||||
const result = buildJobManifest({ ctx, selfPod: mockSelfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_LINKED_ISSUE_IDS")?.value).toBe("a,b,c");
|
||||
});
|
||||
|
||||
it("encodes paperclipWorkspaces / paperclipRuntimeServiceIntents / paperclipRuntimeServices as JSON env", () => {
|
||||
const ctx = {
|
||||
...mockCtx,
|
||||
context: {
|
||||
...mockCtx.context,
|
||||
paperclipWorkspaces: [{ id: "w1" }],
|
||||
paperclipRuntimeServiceIntents: [{ name: "redis" }],
|
||||
paperclipRuntimeServices: [{ name: "redis", url: "redis://r" }],
|
||||
paperclipRuntimePrimaryUrl: "https://primary",
|
||||
},
|
||||
};
|
||||
const result = buildJobManifest({ ctx, selfPod: mockSelfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_WORKSPACES_JSON")?.value).toContain("w1");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_RUNTIME_SERVICE_INTENTS_JSON")?.value).toContain("redis");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_RUNTIME_SERVICES_JSON")?.value).toContain("redis://r");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_RUNTIME_PRIMARY_URL")?.value).toBe("https://primary");
|
||||
});
|
||||
|
||||
it("sets PAPERCLIP_API_KEY from ctx.authToken when provided", () => {
|
||||
const ctx = { ...mockCtx, authToken: "tok_abc" };
|
||||
const result = buildJobManifest({ ctx, selfPod: mockSelfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_API_KEY")?.value).toBe("tok_abc");
|
||||
});
|
||||
|
||||
it("inherits PAPERCLIP_API_URL and PAPERCLIP_DEV_API_KEY from selfPod inheritedEnv", () => {
|
||||
const selfPod = {
|
||||
...mockSelfPod,
|
||||
inheritedEnv: { PAPERCLIP_API_URL: "http://api", PAPERCLIP_DEV_API_KEY: "dev_key" },
|
||||
};
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_API_URL")?.value).toBe("http://api");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_DEV_API_KEY")?.value).toBe("dev_key");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildJobManifest — volume wiring branches", () => {
|
||||
it("mounts the prompt secret volume when promptSecretName is provided", () => {
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod: mockSelfPod, promptSecretName: "prompt-x" });
|
||||
const volumes = result.job.spec?.template.spec?.volumes ?? [];
|
||||
expect(volumes.find((v) => v.name === "prompt-secret")?.secret?.secretName).toBe("prompt-x");
|
||||
});
|
||||
|
||||
it("mounts the data PVC at /paperclip when selfPod has a pvcClaimName", () => {
|
||||
const selfPod = { ...mockSelfPod, pvcClaimName: "paperclip-data" };
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod });
|
||||
const volumes = result.job.spec?.template.spec?.volumes ?? [];
|
||||
expect(volumes.find((v) => v.name === "data")?.persistentVolumeClaim?.claimName).toBe("paperclip-data");
|
||||
const mounts = result.job.spec?.template.spec?.containers[0]?.volumeMounts ?? [];
|
||||
expect(mounts.find((m) => m.name === "data")?.mountPath).toBe("/paperclip");
|
||||
});
|
||||
|
||||
it("mounts inherited secret volumes from selfPod.secretVolumes", () => {
|
||||
const selfPod = {
|
||||
...mockSelfPod,
|
||||
secretVolumes: [{ volumeName: "tls", secretName: "tls-secret", mountPath: "/etc/tls", defaultMode: 0o400 }],
|
||||
};
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod });
|
||||
const volumes = result.job.spec?.template.spec?.volumes ?? [];
|
||||
expect(volumes.find((v) => v.name === "tls")?.secret?.secretName).toBe("tls-secret");
|
||||
const mounts = result.job.spec?.template.spec?.containers[0]?.volumeMounts ?? [];
|
||||
expect(mounts.find((m) => m.name === "tls")).toEqual({ name: "tls", mountPath: "/etc/tls", readOnly: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -312,9 +312,9 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
|
||||
if (input.agentDbClaimName !== undefined) {
|
||||
const dbEnvIdx = envVars.findIndex((e) => e.name === "OPENCODE_DB");
|
||||
if (dbEnvIdx >= 0) {
|
||||
envVars[dbEnvIdx] = { name: "OPENCODE_DB", value: "/opencode-db" };
|
||||
envVars[dbEnvIdx] = { name: "OPENCODE_DB", value: "/opencode-db/opencode.db" };
|
||||
} else {
|
||||
envVars.push({ name: "OPENCODE_DB", value: "/opencode-db" });
|
||||
envVars.push({ name: "OPENCODE_DB", value: "/opencode-db/opencode.db" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,14 @@ vi.mock("@kubernetes/client-node", () => {
|
||||
}
|
||||
}
|
||||
class KubeConfig {
|
||||
loadFromCluster() {}
|
||||
loadFromFile() {}
|
||||
loadFromCluster = mockLoadFromCluster;
|
||||
loadFromFile = mockLoadFromFile;
|
||||
makeApiClient() {
|
||||
return {
|
||||
readNamespacedPersistentVolumeClaim: mockReadNamespacedPVC,
|
||||
deleteNamespacedPersistentVolumeClaim: mockDeleteNamespacedPVC,
|
||||
createNamespacedPersistentVolumeClaim: mockCreateNamespacedPVC,
|
||||
readNamespacedPod: mockReadNamespacedPod,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -51,9 +52,17 @@ vi.mock("@kubernetes/client-node", () => {
|
||||
const mockReadNamespacedPVC = vi.fn();
|
||||
const mockDeleteNamespacedPVC = vi.fn();
|
||||
const mockCreateNamespacedPVC = vi.fn();
|
||||
const mockReadNamespacedPod = vi.fn();
|
||||
const mockLoadFromCluster = vi.fn();
|
||||
const mockLoadFromFile = vi.fn();
|
||||
const mockReadFileSync = vi.fn();
|
||||
|
||||
vi.mock("node:fs", () => ({
|
||||
readFileSync: (...args: unknown[]) => mockReadFileSync(...args),
|
||||
}));
|
||||
|
||||
import * as k8s from "@kubernetes/client-node";
|
||||
import { getPvc, createPvc, deletePvc, resetCache } from "./k8s-client.js";
|
||||
import { getPvc, createPvc, deletePvc, getSelfPodInfo, resetCache } from "./k8s-client.js";
|
||||
|
||||
const ApiException = (k8s as unknown as { ApiException: new <T>(code: number, message: string, body: T, headers?: Record<string, string>) => Error & { code: number; body: T } }).ApiException;
|
||||
|
||||
@@ -143,3 +152,145 @@ describe("createPvc — passes through to SDK", () => {
|
||||
expect(mockCreateNamespacedPVC).toHaveBeenCalledWith({ namespace: "paperclip", body: spec });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSelfPodInfo", () => {
|
||||
const HOSTNAME = "paperclip-test-pod";
|
||||
const NAMESPACE = "paperclip-test";
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.HOSTNAME = HOSTNAME;
|
||||
delete process.env.PAPERCLIP_NAMESPACE;
|
||||
delete process.env.POD_NAMESPACE;
|
||||
mockReadFileSync.mockReturnValue(NAMESPACE);
|
||||
});
|
||||
|
||||
function basePod(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: "paperclip",
|
||||
image: "paperclip:1.0",
|
||||
env: [
|
||||
{ name: "FOO", value: "bar" },
|
||||
{ name: "SECRET_REF", valueFrom: { secretKeyRef: { name: "s", key: "k" } } },
|
||||
],
|
||||
envFrom: [{ configMapRef: { name: "cm" } }],
|
||||
volumeMounts: [
|
||||
{ name: "data", mountPath: "/paperclip" },
|
||||
{ name: "tls-secret", mountPath: "/etc/tls" },
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{ name: "data", persistentVolumeClaim: { claimName: "paperclip-pvc" } },
|
||||
{ name: "tls-secret", secret: { secretName: "tls", defaultMode: 0o400 } },
|
||||
],
|
||||
imagePullSecrets: [{ name: "registry-creds" }, { name: "" }, {}],
|
||||
dnsConfig: { nameservers: ["10.0.0.10"] },
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("introspects the pod and extracts image, env, PVC, secrets, dnsConfig", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.namespace).toBe(NAMESPACE);
|
||||
expect(info.image).toBe("paperclip:1.0");
|
||||
expect(info.pvcClaimName).toBe("paperclip-pvc");
|
||||
expect(info.inheritedEnv).toEqual({ FOO: "bar" });
|
||||
expect(info.inheritedEnvValueFrom).toHaveLength(1);
|
||||
expect(info.inheritedEnvValueFrom[0].name).toBe("SECRET_REF");
|
||||
expect(info.inheritedEnvFrom).toHaveLength(1);
|
||||
expect(info.secretVolumes).toEqual([
|
||||
{ volumeName: "tls-secret", secretName: "tls", mountPath: "/etc/tls", defaultMode: 0o400 },
|
||||
]);
|
||||
// imagePullSecrets with empty name are filtered out
|
||||
expect(info.imagePullSecrets).toEqual([{ name: "registry-creds" }]);
|
||||
expect(info.dnsConfig).toEqual({ nameservers: ["10.0.0.10"] });
|
||||
expect(mockReadNamespacedPod).toHaveBeenCalledWith({ name: HOSTNAME, namespace: NAMESPACE });
|
||||
});
|
||||
|
||||
it("caches the result — second call does not re-query the API", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
await getSelfPodInfo();
|
||||
await getSelfPodInfo();
|
||||
expect(mockReadNamespacedPod).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("prefers PAPERCLIP_NAMESPACE env over service-account file", async () => {
|
||||
process.env.PAPERCLIP_NAMESPACE = "from-env";
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.namespace).toBe("from-env");
|
||||
expect(mockReadFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to POD_NAMESPACE when PAPERCLIP_NAMESPACE not set", async () => {
|
||||
process.env.POD_NAMESPACE = "downward-api";
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.namespace).toBe("downward-api");
|
||||
});
|
||||
|
||||
it("falls back to 'default' when service-account file read throws", async () => {
|
||||
mockReadFileSync.mockImplementation(() => {
|
||||
throw new Error("ENOENT");
|
||||
});
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.namespace).toBe("default");
|
||||
});
|
||||
|
||||
it("throws when HOSTNAME is not set", async () => {
|
||||
delete process.env.HOSTNAME;
|
||||
await expect(getSelfPodInfo()).rejects.toThrow("HOSTNAME env var not set");
|
||||
});
|
||||
|
||||
it("throws when pod has no spec", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue({ spec: null });
|
||||
await expect(getSelfPodInfo()).rejects.toThrow("has no spec");
|
||||
});
|
||||
|
||||
it("throws when main container has no image", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue({
|
||||
spec: { containers: [{ name: "paperclip", image: "" }] },
|
||||
});
|
||||
await expect(getSelfPodInfo()).rejects.toThrow("has no container image");
|
||||
});
|
||||
|
||||
it("falls back to first container when no container is named 'paperclip'", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue({
|
||||
spec: { containers: [{ name: "other", image: "other:1.0" }] },
|
||||
});
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.image).toBe("other:1.0");
|
||||
});
|
||||
|
||||
it("returns null pvcClaimName when no /paperclip mount exists", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue({
|
||||
spec: { containers: [{ name: "paperclip", image: "p:1", volumeMounts: [] }] },
|
||||
});
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.pvcClaimName).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null pvcClaimName when /paperclip mount is not backed by a PVC", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue({
|
||||
spec: {
|
||||
containers: [{ name: "paperclip", image: "p:1", volumeMounts: [{ name: "data", mountPath: "/paperclip" }] }],
|
||||
volumes: [{ name: "data", emptyDir: {} }],
|
||||
},
|
||||
});
|
||||
const info = await getSelfPodInfo();
|
||||
expect(info.pvcClaimName).toBeNull();
|
||||
});
|
||||
|
||||
it("uses kubeconfig file path when provided (not in-cluster)", async () => {
|
||||
mockReadNamespacedPod.mockResolvedValue(basePod());
|
||||
await getSelfPodInfo("/tmp/kubeconfig.yaml");
|
||||
expect(mockLoadFromFile).toHaveBeenCalledWith("/tmp/kubeconfig.yaml");
|
||||
expect(mockLoadFromCluster).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,3 +182,45 @@ describe("isOpenCodeUnknownSessionError", () => {
|
||||
expect(isOpenCodeUnknownSessionError(stdout, "")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseOpenCodeJsonl — errorText fallback paths", () => {
|
||||
it("uses nested data.message when top-level message is missing", () => {
|
||||
const stdout = JSON.stringify({
|
||||
type: "error",
|
||||
error: { data: { message: "nested issue" } },
|
||||
sessionID: "ses_x",
|
||||
});
|
||||
const result = parseOpenCodeJsonl(stdout);
|
||||
expect(result.errorMessage).toContain("nested issue");
|
||||
});
|
||||
|
||||
it("uses error.name when no message or nested message", () => {
|
||||
const stdout = JSON.stringify({
|
||||
type: "error",
|
||||
error: { name: "ProviderAuthError" },
|
||||
sessionID: "ses_x",
|
||||
});
|
||||
const result = parseOpenCodeJsonl(stdout);
|
||||
expect(result.errorMessage).toContain("ProviderAuthError");
|
||||
});
|
||||
|
||||
it("uses error.code when no message/name", () => {
|
||||
const stdout = JSON.stringify({
|
||||
type: "error",
|
||||
error: { code: "E_TIMEOUT" },
|
||||
sessionID: "ses_x",
|
||||
});
|
||||
const result = parseOpenCodeJsonl(stdout);
|
||||
expect(result.errorMessage).toContain("E_TIMEOUT");
|
||||
});
|
||||
|
||||
it("falls back to JSON.stringify of the error object when nothing matches", () => {
|
||||
const stdout = JSON.stringify({
|
||||
type: "error",
|
||||
error: { unexpectedShape: { foo: "bar" } },
|
||||
sessionID: "ses_x",
|
||||
});
|
||||
const result = parseOpenCodeJsonl(stdout);
|
||||
expect(result.errorMessage).toContain("unexpectedShape");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -323,3 +323,41 @@ describe("parseStdoutLine", () => {
|
||||
expect(parseStdoutLine(line, TS)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseStdoutLine — error edge cases", () => {
|
||||
const TS_ERR = "2026-04-25T22:00:00.000Z";
|
||||
|
||||
it("returns stdout entry when JSON parses to a primitive (not an object)", () => {
|
||||
const result = parseStdoutLine("42", TS_ERR);
|
||||
// safeJsonParse returns null for non-object → falls through to stdout entry
|
||||
expect(result).toEqual([{ kind: "stdout", ts: TS_ERR, text: "42" }]);
|
||||
});
|
||||
|
||||
it("returns empty for text event with empty text", () => {
|
||||
const line = JSON.stringify({ type: "text", part: { text: "" } });
|
||||
expect(parseStdoutLine(line, TS_ERR)).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty for assistant event with no content blocks", () => {
|
||||
const line = JSON.stringify({ type: "assistant", part: { message: { content: null } } });
|
||||
expect(parseStdoutLine(line, TS_ERR)).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty for error event whose error field is an empty string", () => {
|
||||
const line = JSON.stringify({ type: "error", error: "" });
|
||||
expect(parseStdoutLine(line, TS_ERR)).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses error.code fallback when error has no message/data/name", () => {
|
||||
const line = JSON.stringify({ type: "error", error: { code: "E_FOO" } });
|
||||
const result = parseStdoutLine(line, TS_ERR);
|
||||
expect(result).toEqual([{ kind: "stderr", ts: TS_ERR, text: "E_FOO" }]);
|
||||
});
|
||||
|
||||
it("falls back to JSON.stringify of error object when no known field", () => {
|
||||
const line = JSON.stringify({ type: "error", error: { somethingElse: "x" } });
|
||||
const result = parseStdoutLine(line, TS_ERR);
|
||||
expect(result[0].kind).toBe("stderr");
|
||||
expect((result[0] as { text: string }).text).toContain("somethingElse");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user