diff --git a/package-lock.json b/package-lock.json index 0e9edad2..a78113d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "electron-updater": "^6.8.9", "node-forge": "^1.4.0", "openai": "^6.27.0", - "undici": "^7.27.2" + "pm2": "^7.0.3", + "undici": "^7.29.0" }, "devDependencies": { "@dnd-kit/core": "^6.3.1", @@ -36,7 +37,7 @@ "electron": "^42.3.3", "electron-builder": "^26.8.1", "esbuild": "^0.27.7", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.1", "lucide-react": "^1.17.0", "motion": "^12.40.0", "react": "^18.3.1", @@ -1655,98 +1656,6 @@ "node": ">=18" } }, - "node_modules/@pm2/agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", - "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", - "license": "AGPL-3.0", - "dependencies": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.1.0", - "fclone": "~1.0.11", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.4.0", - "semver": "~7.5.0", - "ws": "~7.5.10" - } - }, - "node_modules/@pm2/agent/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pm2/agent/node_modules/dayjs": { - "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", - "license": "MIT" - }, - "node_modules/@pm2/agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@pm2/agent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pm2/agent/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@pm2/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", @@ -1759,78 +1668,6 @@ "node": ">= 0.8.0" } }, - "node_modules/@pm2/io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", - "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", - "license": "Apache-2", - "dependencies": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@pm2/io/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/@pm2/io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@pm2/io/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT" - }, - "node_modules/@pm2/io/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "license": "Apache-2.0" - }, "node_modules/@pm2/js-api": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.1.tgz", @@ -1873,12 +1710,6 @@ } } }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT" - }, "node_modules/@pm2/pm2-version-check": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", @@ -2640,15 +2471,6 @@ "amp": "0.3.1" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3202,12 +3024,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bodec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", - "license": "MIT" - }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -3217,18 +3033,6 @@ "license": "MIT", "optional": true }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -3269,6 +3073,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, "license": "MIT" }, "node_modules/builder-util": { @@ -3405,12 +3210,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/charm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", - "license": "MIT/X11" - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -3695,12 +3494,6 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/culvert": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", - "license": "MIT" - }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", @@ -4679,18 +4472,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -4725,6 +4506,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4833,7 +4615,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -4894,9 +4678,9 @@ } }, "node_modules/eventemitter2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", - "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "license": "MIT" }, "node_modules/eventemitter3": { @@ -4982,9 +4766,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -5039,12 +4823,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fclone": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", - "license": "MIT" - }, "node_modules/file-selector": { "version": "0.1.19", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", @@ -5116,9 +4894,9 @@ } }, "node_modules/find-my-way": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", - "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz", + "integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5250,6 +5028,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5336,18 +5115,6 @@ "node": ">= 14" } }, - "node_modules/git-node-fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", - "license": "MIT" - }, - "node_modules/git-sha1": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", - "license": "MIT" - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -5542,6 +5309,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5710,9 +5478,9 @@ } }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", + "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", "license": "MIT", "engines": { "node": ">= 12" @@ -5739,21 +5507,6 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5861,18 +5614,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-git": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", - "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", - "license": "MIT", - "dependencies": { - "bodec": "^0.1.0", - "culvert": "^0.1.2", - "git-sha1": "^0.1.2", - "pako": "^0.2.5" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5880,9 +5621,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", @@ -5937,8 +5678,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", @@ -6346,6 +6086,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -6511,6 +6252,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -6582,12 +6335,6 @@ "integrity": "sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A==", "license": "MIT" }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -6660,56 +6407,12 @@ "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", "license": "MIT" }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, - "node_modules/needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/netmask": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", @@ -6988,12 +6691,6 @@ "node": ">= 14" } }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "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", @@ -7014,12 +6711,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -7093,15 +6784,15 @@ } }, "node_modules/pidusage": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", - "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-4.0.1.tgz", + "integrity": "sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==", "license": "MIT", "dependencies": { "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/pidusage/node_modules/safe-buffer": { @@ -7240,16 +6931,16 @@ } }, "node_modules/pm2": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.14.tgz", - "integrity": "sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-7.0.3.tgz", + "integrity": "sha512-zRJOdburpb9OEPB0uqoNT8C1Gp7hPJPVy4Kr67XJNuT9UlMQcOt1WXrYQUmwqKPHk8FyauvP1CPhqoCrCaPw0Q==", "license": "AGPL-3.0", "dependencies": { - "@pm2/agent": "~2.1.1", "@pm2/blessed": "0.1.81", - "@pm2/io": "~6.1.0", - "@pm2/js-api": "~0.8.0", - "@pm2/pm2-version-check": "^1.0.4", + "@pm2/js-api": "0.8.1", + "@pm2/pm2-version-check": "1.0.4", + "amp": "0.3.1", + "amp-message": "0.1.2", "ansis": "4.0.0-node10", "async": "3.2.6", "chokidar": "3.6.0", @@ -7258,22 +6949,15 @@ "croner": "4.1.97", "dayjs": "1.11.15", "debug": "4.4.3", - "enquirer": "2.3.6", - "eventemitter2": "5.0.1", - "fclone": "1.0.11", - "js-yaml": "4.1.1", - "mkdirp": "1.0.4", - "needle": "2.4.0", - "pidusage": "3.0.2", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.1", - "pm2-deploy": "~1.0.2", - "pm2-multimeter": "^0.1.2", - "promptly": "2.2.0", + "eventemitter2": "6.4.9", + "fast-json-patch": "3.1.1", + "js-yaml": "4.3.0", + "pidusage": "4.0.1", + "pm2-deploy": "1.0.2", + "proxy-agent": "6.5.0", "semver": "7.7.2", - "source-map-support": "0.5.21", - "sprintf-js": "1.1.2", - "vizion": "~2.2.1" + "tx2": "1.0.5", + "ws": "8.21.0" }, "bin": { "pm2": "bin/pm2", @@ -7282,37 +6966,7 @@ "pm2-runtime": "bin/pm2-runtime" }, "engines": { - "node": ">=16.0.0" - }, - "optionalDependencies": { - "pm2-sysmonit": "^1.2.8" - } - }, - "node_modules/pm2-axon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", - "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", - "license": "MIT", - "dependencies": { - "amp": "~0.3.1", - "amp-message": "~0.1.1", - "debug": "^4.3.1", - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=5" - } - }, - "node_modules/pm2-axon-rpc": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", - "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.1" - }, - "engines": { - "node": ">=5" + "node": ">=18.0.0" } }, "node_modules/pm2-deploy": { @@ -7328,93 +6982,12 @@ "node": ">=4.0.0" } }, - "node_modules/pm2-multimeter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", - "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", - "license": "MIT/X11", - "dependencies": { - "charm": "~0.1.1" - } - }, - "node_modules/pm2-sysmonit": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", - "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", - "license": "Apache", - "optional": true, - "dependencies": { - "async": "^3.2.0", - "debug": "^4.3.1", - "pidusage": "^2.0.21", - "systeminformation": "^5.7", - "tx2": "~1.0.4" - } - }, - "node_modules/pm2-sysmonit/node_modules/pidusage": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", - "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pm2-sysmonit/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, "node_modules/pm2/node_modules/commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "license": "MIT" }, - "node_modules/pm2/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/pm2/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/pm2/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -7427,12 +7000,6 @@ "node": ">=10" } }, - "node_modules/pm2/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "license": "BSD-3-Clause" - }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", @@ -7597,15 +7164,6 @@ "node": ">=10" } }, - "node_modules/promptly": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", - "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", - "license": "MIT", - "dependencies": { - "read": "^1.0.4" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7661,19 +7219,19 @@ "license": "MIT" }, "node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", + "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", + "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" + "socks-proxy-agent": "^8.0.5" }, "engines": { "node": ">= 14" @@ -7995,18 +7553,6 @@ "node": ">=6" } }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "license": "ISC", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", @@ -8121,20 +7667,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-in-the-middle": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", - "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", @@ -8165,27 +7697,6 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", "license": "MIT" }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -8526,16 +8037,11 @@ "node": ">=8" } }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "license": "BSD-2-Clause" - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/simple-concat": { @@ -8647,6 +8153,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -8666,6 +8173,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -8813,45 +8321,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/systeminformation": { - "version": "5.31.12", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.12.tgz", - "integrity": "sha512-qnTtO5wHrKeKE/MvQ6iIt6XAV+5fgt/kPIQf27DYgjVQQuHUfWkV4Gu6k04ZpEzAMuyQ3ZsovY7Ivhp+E9JyWw==", - "license": "MIT", - "optional": true, - "os": [ - "darwin", - "linux", - "win32", - "freebsd", - "openbsd", - "netbsd", - "sunos", - "android" - ], - "bin": { - "systeminformation": "lib/cli.js" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "Buy me a coffee", - "url": "https://www.buymeacoffee.com/systeminfo" - } - }, "node_modules/tailwind-merge": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", @@ -9180,7 +8649,6 @@ "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", "license": "MIT", - "optional": true, "dependencies": { "json-stringify-safe": "^5.0.1" } @@ -9214,9 +8682,9 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -9364,30 +8832,6 @@ "@math.gl/web-mercator": "^3.5.5" } }, - "node_modules/vizion": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", - "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", - "license": "Apache-2.0", - "dependencies": { - "async": "^2.6.3", - "git-node-fs": "^1.0.0", - "ini": "^1.3.5", - "js-git": "^0.7.8" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/vizion/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -9507,6 +8951,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC" }, "node_modules/yargs": { @@ -9560,7 +9005,7 @@ "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "node-forge": "^1.4.0", - "undici": "^7.27.2" + "undici": "^7.29.0" }, "bin": { "ccr": "dist/main/cli.js" @@ -9577,8 +9022,8 @@ "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "node-forge": "^1.4.0", - "pm2": "^6.0.13", - "undici": "^7.27.2" + "pm2": "^7.0.3", + "undici": "^7.29.0" }, "bin": { "ccr-core-server": "dist/main/server.js" diff --git a/package.json b/package.json index 6e48a1d1..465c56f3 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,8 @@ "electron-updater": "^6.8.9", "node-forge": "^1.4.0", "openai": "^6.27.0", - "undici": "^7.27.2" + "pm2": "^7.0.3", + "undici": "^7.29.0" }, "devDependencies": { "@dnd-kit/core": "^6.3.1", @@ -96,7 +97,7 @@ "electron": "^42.3.3", "electron-builder": "^26.8.1", "esbuild": "^0.27.7", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.1", "lucide-react": "^1.17.0", "motion": "^12.40.0", "react": "^18.3.1", @@ -109,6 +110,18 @@ "typescript": "^5.9.3" }, "overrides": { - "@types/react": "$@types/react" + "@types/react": "$@types/react", + "fast-uri@3.1.2": "3.1.5", + "find-my-way@9.6.0": "9.7.0", + "js-yaml": "$js-yaml", + "minimatch@10.2.5": { + "brace-expansion": "5.0.9" + }, + "pm2": { + "js-yaml": "4.3.1" + }, + "socks": { + "ip-address": "10.5.0" + } } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 111c6ceb..3a63ae10 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -45,6 +45,6 @@ "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "node-forge": "^1.4.0", - "undici": "^7.27.2" + "undici": "^7.29.0" } } diff --git a/packages/core/package.json b/packages/core/package.json index b954dd71..065d4515 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,7 @@ "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "node-forge": "^1.4.0", - "pm2": "^6.0.13", - "undici": "^7.27.2" + "pm2": "^7.0.3", + "undici": "^7.29.0" } } diff --git a/packages/core/src/gateway/application/gateway-service.ts b/packages/core/src/gateway/application/gateway-service.ts index d41a68c5..64cb3230 100644 --- a/packages/core/src/gateway/application/gateway-service.ts +++ b/packages/core/src/gateway/application/gateway-service.ts @@ -6,13 +6,14 @@ import { randomUUID } from "node:crypto"; import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; import type { ApiKeyConfig, AppConfig, GatewayStatus, RouteScriptTestRequest, RouteScriptTestResult, RouteScriptValidationRequest, RouteScriptValidationResult, RouterRule } from "@ccr/core/contracts/app"; import { NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, hasAvailableGatewayModels } from "@ccr/core/contracts/app"; +import { loadAppConfig } from "@ccr/core/config/config"; import { backendService } from "@ccr/core/plugins/backend-service"; import { getSystemProxyUrlForProtocol } from "@ccr/core/proxy/system-proxy-fetch"; import { pluginService } from "@ccr/core/plugins/service"; import { proxyService } from "@ccr/core/proxy/service"; import { ClaudeCodeRouterPlugin } from "@ccr/core/gateway/claude-code-router-plugin"; import { compileCoreGatewayConfig } from "@ccr/core/gateway/core-runtime/config-compiler"; -import { isAddressInUseMessage, probeExistingCcrGateway } from "@ccr/core/gateway/existing-gateway-probe"; +import { isAddressInUseMessage, probeExistingCcrGateway, reloadExistingCcrGatewayConfig } from "@ccr/core/gateway/existing-gateway-probe"; import { closeServer, formatError } from "@ccr/core/gateway/http/io"; import { RawTraceSynchronizer } from "@ccr/core/observability/raw-trace-sync"; import { GatewayBillingSynchronizer } from "@ccr/core/usage/billing-sync"; @@ -21,6 +22,8 @@ import { coreGatewayAuthHeader } from "@ccr/core/gateway/internal/shared"; import type { BrowserAutomationMcpIntegration, BrowserWebSearchMcpIntegration, GatewayStopOptions } from "@ccr/core/gateway/internal/shared"; import { GatewayRequestPipeline } from "@ccr/core/gateway/request/pipeline"; import { GatewayHttpRequestHandler } from "@ccr/core/gateway/http/request-handler"; +import { gatewayRuntimeConfigRevision } from "@ccr/core/gateway/runtime-config-control"; +import { shouldRestartGatewayForRuntimeConfigChange } from "@ccr/core/gateway/runtime-change"; import { RouteScriptRuntime } from "@ccr/core/routing/route-script-runtime"; import { buildRouteScriptInput } from "@ccr/core/routing/route-script-context"; import { compileRouterConfig } from "@ccr/core/routing/config-compiler"; @@ -61,6 +64,10 @@ class GatewayService { getBrowserAutomationMcpIntegration: () => this.browserAutomationMcpIntegration, getConfig: () => this.config, getPlugin: () => this.plugin, + getRuntimeConfigControlStatus: () => ({ + ...(this.runtimeConfigReloadError ? { lastError: this.runtimeConfigReloadError } : {}), + revision: gatewayRuntimeConfigRevision(this.config) + }), getStatus: () => ({ coreEndpoint: this.status.coreEndpoint, coreManagedExternally: this.status.coreManagedExternally, @@ -70,6 +77,9 @@ class GatewayService { handleRawTraceSync: (request, response) => this.rawTraceSynchronizer.handle(request, response), handleBillingUsageSync: (request, response) => this.billingSynchronizer.handle(request, response), proxyRequest: (request, response, path, apiKey) => this.proxyRequest(request, response, path, apiKey), + requestRuntimeConfigReload: (expectedRevision, forceRestart) => { + this.schedulePersistedRuntimeConfigReload(expectedRevision, forceRestart); + }, replayContextArchive: (input) => this.requestPipeline.replayContextArchive(input) }); @@ -90,11 +100,14 @@ class GatewayService { private child?: ChildProcess; private config?: AppConfig; private coreAuthToken = ""; + private externalGatewayApiKey?: string; private plugin?: ClaudeCodeRouterPlugin; private readonly rawTraceSynchronizer = new RawTraceSynchronizer({ getConfig: () => this.config }); private readonly routeScriptRuntime = new RouteScriptRuntime(); + private runtimeConfigReloadError?: string; + private runtimeConfigReloadQueue: Promise = Promise.resolve(); private server?: Server; private status: GatewayStatus = { coreEndpoint: "", @@ -216,6 +229,7 @@ class GatewayService { pid: this.child?.pid, state: "running" }; + this.runtimeConfigReloadError = undefined; return this.status; } catch (error) { await this.stop(); @@ -235,7 +249,12 @@ class GatewayService { if (currentStatus.gatewayManagedExternally) { const existingGateway = await probeExistingCcrGateway(config); if (existingGateway.state === "usable") { - this.markExternalGatewayRunning(config, existingGateway.endpoint); + this.markExternalGatewayRunning(config, existingGateway.endpoint, existingGateway.apiKey); + try { + await this.reloadExternalGatewayConfig(config, false); + } catch { + return this.getStatus(); + } return this.getStatus(); } } @@ -255,14 +274,28 @@ class GatewayService { return status; } - this.markExternalGatewayRunning(config, existingGateway.endpoint); + this.markExternalGatewayRunning(config, existingGateway.endpoint, existingGateway.apiKey); + try { + await this.reloadExternalGatewayConfig(config, false); + } catch { + return this.getStatus(); + } return this.getStatus(); } + async restart(config: AppConfig): Promise { + if (this.status.gatewayManagedExternally) { + await this.reloadExternalGatewayConfig(config, true); + return this.getStatus(); + } + return this.start(config); + } + async stop(options: GatewayStopOptions = {}): Promise { const child = this.child; this.child = undefined; this.coreAuthToken = ""; + this.externalGatewayApiKey = undefined; if (child && !child.killed) { child.kill(); } @@ -311,7 +344,7 @@ class GatewayService { async updateConfig(config: AppConfig): Promise { assertLoopbackCoreHost(config.gateway.coreHost); if (this.status.gatewayManagedExternally) { - this.markExternalGatewayRunning(config, endpoint(config.gateway.host, config.gateway.port)); + await this.reloadExternalGatewayConfig(config, false); return; } @@ -452,11 +485,65 @@ class GatewayService { return this.requestPipeline.proxyRequest(request, response, path, apiKey); } - private markExternalGatewayRunning(config: AppConfig, externalEndpoint: string): void { + private async reloadExternalGatewayConfig(config: AppConfig, forceRestart: boolean): Promise { + const currentEndpoint = this.status.endpoint || endpoint(config.gateway.host, config.gateway.port); + try { + const externalGateway = await reloadExistingCcrGatewayConfig( + currentEndpoint, + config, + this.externalGatewayApiKey, + { forceRestart } + ); + this.markExternalGatewayRunning(config, externalGateway.endpoint, externalGateway.apiKey); + } catch (error) { + const message = `Failed to update the externally managed CCR gateway: ${formatError(error)}`; + this.status = { + ...this.status, + lastError: message, + state: "error" + }; + throw new Error(message, { cause: error }); + } + } + + private schedulePersistedRuntimeConfigReload(expectedRevision: string, forceRestart: boolean): void { + this.runtimeConfigReloadError = undefined; + this.runtimeConfigReloadQueue = this.runtimeConfigReloadQueue.then( + () => this.reloadPersistedRuntimeConfig(expectedRevision, forceRestart), + () => this.reloadPersistedRuntimeConfig(expectedRevision, forceRestart) + ); + } + + private async reloadPersistedRuntimeConfig(expectedRevision: string, forceRestart: boolean): Promise { + try { + const nextConfig = await loadAppConfig(); + const actualRevision = gatewayRuntimeConfigRevision(nextConfig); + if (actualRevision !== expectedRevision) { + throw new Error(`Persisted configuration revision ${actualRevision || "(missing)"} does not match the requested revision ${expectedRevision}.`); + } + const restartRequired = forceRestart || !this.config || + shouldRestartGatewayForRuntimeConfigChange(this.config, nextConfig); + if (restartRequired) { + const status = await this.start(nextConfig); + if (status.state === "error") { + throw new Error(status.lastError || "CCR gateway failed to restart with the updated configuration."); + } + } else { + await this.updateConfig(nextConfig); + } + this.runtimeConfigReloadError = undefined; + } catch (error) { + this.runtimeConfigReloadError = formatError(error); + console.error(`[gateway] Failed to reload persisted runtime configuration: ${this.runtimeConfigReloadError}`); + } + } + + private markExternalGatewayRunning(config: AppConfig, externalEndpoint: string, apiKey?: string): void { this.config = config; this.child = undefined; this.server = undefined; this.coreAuthToken = ""; + this.externalGatewayApiKey = apiKey; this.status = { coreEndpoint: endpoint(config.gateway.coreHost, config.gateway.corePort), endpoint: externalEndpoint, diff --git a/packages/core/src/gateway/core-runtime/supervisor.ts b/packages/core/src/gateway/core-runtime/supervisor.ts index 3cb63e26..ba249a03 100644 --- a/packages/core/src/gateway/core-runtime/supervisor.ts +++ b/packages/core/src/gateway/core-runtime/supervisor.ts @@ -522,7 +522,15 @@ function mergeNoProxy(current: string | undefined, values: string[]): string { } export function endpoint(host: string, port: number): string { - const endpointHost = host === "0.0.0.0" ? "127.0.0.1" : host; + const unwrappedHost = host.startsWith("[") && host.endsWith("]") + ? host.slice(1, -1) + : host; + const connectHost = !unwrappedHost || unwrappedHost === "0.0.0.0" + ? "127.0.0.1" + : unwrappedHost === "::" + ? "::1" + : unwrappedHost; + const endpointHost = connectHost.includes(":") ? `[${connectHost}]` : connectHost; return `http://${endpointHost}:${port}`; } diff --git a/packages/core/src/gateway/existing-gateway-probe.ts b/packages/core/src/gateway/existing-gateway-probe.ts index 216ce083..e31e12cd 100644 --- a/packages/core/src/gateway/existing-gateway-probe.ts +++ b/packages/core/src/gateway/existing-gateway-probe.ts @@ -1,4 +1,6 @@ import type { ApiKeyConfig, AppConfig } from "@ccr/core/contracts/app"; +import { endpoint as gatewayEndpoint } from "@ccr/core/gateway/core-runtime/supervisor"; +import { gatewayRuntimeConfigControlPath, gatewayRuntimeConfigRevision } from "@ccr/core/gateway/runtime-config-control"; export type ExistingCcrGatewayProbe = | { endpoint: string; reason?: string; state: "unavailable" } @@ -14,6 +16,7 @@ type ExistingGatewayHttpProbe = { }; const existingGatewayFetchAttempts = 3; +const runtimeConfigReloadTimeoutMs = 20_000; export async function probeExistingCcrGateway( config: Pick @@ -71,7 +74,78 @@ export async function probeExistingCcrGateway( export function publicGatewayEndpoint(config: Pick): string { const host = probeGatewayHost(config.gateway.host); - return `http://${formatEndpointHost(host)}:${config.gateway.port}/`; + return gatewayEndpoint(host, config.gateway.port); +} + +export async function reloadExistingCcrGatewayConfig( + currentEndpoint: string, + config: AppConfig, + currentApiKey: string | undefined, + options: { forceRestart?: boolean } = {} +): Promise<{ apiKey: string; endpoint: string }> { + const configRevision = gatewayRuntimeConfigRevision(config); + if (!configRevision) { + throw new Error("Cannot determine the saved CCR configuration revision."); + } + const currentKey = currentApiKey?.trim(); + if (!currentKey) { + throw new Error("The API key accepted by the externally managed CCR gateway is unavailable."); + } + + if (options.forceRestart !== true) { + const currentStatus = await fetchExistingGateway(currentEndpoint, gatewayRuntimeConfigControlPath, { + headers: { authorization: `Bearer ${currentKey}` } + }, 1, 700); + if (currentStatus.status === 200 && isRecord(currentStatus.payload) && + currentStatus.payload.revision === configRevision) { + return { apiKey: currentKey, endpoint: publicGatewayEndpoint(config) }; + } + } + + const submission = await fetchExistingGateway(currentEndpoint, gatewayRuntimeConfigControlPath, { + body: JSON.stringify({ + configRevision, + forceRestart: options.forceRestart === true + }), + headers: { + authorization: `Bearer ${currentKey}`, + "content-type": "application/json" + }, + method: "POST" + }); + if (submission.status === 404) { + throw new Error("The running CCR gateway does not support runtime configuration reloads. Restart that gateway process once, then try again."); + } + if (submission.status !== 200 && submission.status !== 202) { + const detail = readGatewayErrorMessage(submission.payload) || submission.reason || + `HTTP ${submission.status ?? 0}`; + throw new Error(`The running CCR gateway rejected the configuration reload request: ${detail}`); + } + + const expectedEndpoint = publicGatewayEndpoint(config); + const apiKeys = externalGatewayReloadApiKeys(config, currentKey); + const deadline = Date.now() + runtimeConfigReloadTimeoutMs; + let lastReason: string | undefined; + while (Date.now() < deadline) { + for (const apiKey of apiKeys) { + const status = await fetchExistingGateway(expectedEndpoint, gatewayRuntimeConfigControlPath, { + headers: { authorization: `Bearer ${apiKey}` } + }, 1, 700); + if (status.status === 200 && isRecord(status.payload)) { + if (status.payload.revision === configRevision) { + return { apiKey, endpoint: expectedEndpoint }; + } + if (typeof status.payload.lastError === "string" && status.payload.lastError.trim()) { + throw new Error(`The running CCR gateway could not apply the saved configuration: ${status.payload.lastError}`); + } + } + if (status.status !== 401 && status.status !== 403) { + lastReason = status.reason || (status.status ? `HTTP ${status.status}` : lastReason); + } + } + await wait(100); + } + throw new Error(`Timed out waiting for the running CCR gateway to load configuration revision ${configRevision}${lastReason ? ` (${lastReason})` : ""}.`); } export function isAddressInUseMessage(message: string | undefined): boolean { @@ -81,12 +155,14 @@ export function isAddressInUseMessage(message: string | undefined): boolean { async function fetchExistingGateway( endpoint: string, pathname: string, - init: RequestInit = {} + init: RequestInit = {}, + attempts = existingGatewayFetchAttempts, + timeoutMs = 1200 ): Promise { let reason: string | undefined; - for (let attempt = 0; attempt < existingGatewayFetchAttempts; attempt += 1) { + for (let attempt = 0; attempt < attempts; attempt += 1) { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 1200); + const timeout = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(new URL(pathname, endpoint).toString(), { ...init, @@ -105,6 +181,17 @@ async function fetchExistingGateway( return { reason }; } +function externalGatewayReloadApiKeys( + config: Pick, + currentApiKey: string +): string[] { + const apiKeys = existingGatewayApiKeyCandidates(config).map((candidate) => candidate.key); + if (!apiKeys.includes(currentApiKey)) { + apiKeys.push(currentApiKey); + } + return apiKeys; +} + function existingGatewayApiKeyCandidates(config: Pick): ApiKeyConfig[] { const candidates = [ ...(Array.isArray(config.APIKEYS) ? config.APIKEYS : []), @@ -175,10 +262,6 @@ function probeGatewayHost(host: string): string { return host; } -function formatEndpointHost(host: string): string { - return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; -} - function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } @@ -186,3 +269,7 @@ function isRecord(value: unknown): value is Record { function formatError(error: unknown): string { return error instanceof Error ? error.message : String(error); } + +async function wait(milliseconds: number): Promise { + await new Promise((resolve) => setTimeout(resolve, milliseconds)); +} diff --git a/packages/core/src/gateway/http/request-handler.ts b/packages/core/src/gateway/http/request-handler.ts index 37010875..9e71a579 100644 --- a/packages/core/src/gateway/http/request-handler.ts +++ b/packages/core/src/gateway/http/request-handler.ts @@ -14,6 +14,7 @@ import { type ContextArchiveReplayExecutor } from "@ccr/core/gateway/context-archive"; import { ccrRemoteControlPathPrefix, ccrRemoteControlService } from "@ccr/core/gateway/remote-control-service"; +import { gatewayRuntimeConfigControlPath } from "@ccr/core/gateway/runtime-config-control"; import { authorize, claudeCodeWifTokenPath, handleClaudeCodeWifTokenRequest, reserveApiKeyLimits } from "@ccr/core/gateway/auth/api-key-authorizer"; import { parseJsonObject, readRequestBody, sendJson } from "@ccr/core/gateway/http/io"; import { shouldRecordRequestLogs } from "@ccr/core/observability/raw-trace-sync"; @@ -27,10 +28,12 @@ export type GatewayHttpRequestHandlerDependencies = { getBrowserAutomationMcpIntegration: () => BrowserAutomationMcpIntegration | undefined; getConfig: () => AppConfig | undefined; getPlugin: () => ClaudeCodeRouterPlugin | undefined; + getRuntimeConfigControlStatus: () => { lastError?: string; revision?: string }; getStatus: () => { coreEndpoint: string; coreManagedExternally?: boolean; endpoint: string; state: string }; handleBillingUsageSync: (request: IncomingMessage, response: ServerResponse) => Promise; handleRawTraceSync: (request: IncomingMessage, response: ServerResponse) => Promise; proxyRequest: (request: IncomingMessage, response: ServerResponse, path: string, apiKey?: ApiKeyConfig) => Promise; + requestRuntimeConfigReload: (expectedRevision: string, forceRestart: boolean) => void; replayContextArchive: ContextArchiveReplayExecutor; }; @@ -40,10 +43,12 @@ export class GatewayHttpRequestHandler { private get browserAutomationMcpIntegration() { return this.dependencies.getBrowserAutomationMcpIntegration(); } private get config() { return this.dependencies.getConfig(); } private get plugin() { return this.dependencies.getPlugin(); } + private get runtimeConfigControlStatus() { return this.dependencies.getRuntimeConfigControlStatus(); } private get status() { return this.dependencies.getStatus(); } private handleBillingUsageSync(request: IncomingMessage, response: ServerResponse) { return this.dependencies.handleBillingUsageSync(request, response); } private handleRawTraceSync(request: IncomingMessage, response: ServerResponse) { return this.dependencies.handleRawTraceSync(request, response); } private proxyRequest(request: IncomingMessage, response: ServerResponse, path: string, apiKey?: ApiKeyConfig) { return this.dependencies.proxyRequest(request, response, path, apiKey); } + private requestRuntimeConfigReload(expectedRevision: string, forceRestart: boolean) { return this.dependencies.requestRuntimeConfigReload(expectedRevision, forceRestart); } private replayContextArchive(input: Parameters[0]) { return this.dependencies.replayContextArchive(input); } async handleRequest(request: IncomingMessage, response: ServerResponse): Promise { @@ -75,6 +80,46 @@ export class GatewayHttpRequestHandler { return; } + if (path === gatewayRuntimeConfigControlPath) { + const authorization = await authorize(request, response, this.config); + if (!authorization.ok) { + return; + } + if (!this.config.APIKEY || authorization.apiKey?.key !== this.config.APIKEY) { + sendJson(response, 403, { error: { message: "The primary CCR API key is required for runtime configuration control." } }); + return; + } + if (request.method === "GET") { + sendJson(response, 200, { + ...this.runtimeConfigControlStatus, + state: this.status.state + }); + return; + } + if (request.method !== "POST") { + sendJson(response, 405, { error: { message: "Method not allowed." } }); + return; + } + const body = parseJsonObject(await readRequestBody(request)); + const expectedRevision = typeof body.configRevision === "string" + ? body.configRevision.trim() + : ""; + if (!/^[a-f0-9]{64}$/i.test(expectedRevision)) { + sendJson(response, 400, { error: { message: "A valid configRevision is required." } }); + return; + } + const forceRestart = body.forceRestart === true; + response.once("finish", () => { + this.requestRuntimeConfigReload(expectedRevision, forceRestart); + }); + sendJson(response, 202, { + accepted: true, + configRevision: expectedRevision, + restarting: forceRestart + }); + return; + } + if (path === ccrRemoteControlPathPrefix || path.startsWith(`${ccrRemoteControlPathPrefix}/`)) { const authorization = await authorize(request, response, this.config); if (!authorization.ok) { diff --git a/packages/core/src/gateway/runtime-config-control.ts b/packages/core/src/gateway/runtime-config-control.ts new file mode 100644 index 00000000..8cafdcec --- /dev/null +++ b/packages/core/src/gateway/runtime-config-control.ts @@ -0,0 +1,28 @@ +import { createHash } from "node:crypto"; +import type { AppConfig } from "@ccr/core/contracts/app"; + +export const gatewayRuntimeConfigControlPath = "/__ccr/runtime/config"; + +export function gatewayRuntimeConfigRevision(config: AppConfig | undefined): string | undefined { + if (!config) { + return undefined; + } + return createHash("sha256") + .update(stableJson(config)) + .digest("hex"); +} + +function stableJson(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map(stableJson).join(",")}]`; + } + if (value && typeof value === "object") { + const record = value as Record; + return `{${Object.keys(record) + .sort() + .filter((key) => record[key] !== undefined) + .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`) + .join(",")}}`; + } + return JSON.stringify(value) ?? "null"; +} diff --git a/packages/core/src/profiles/launch-service.ts b/packages/core/src/profiles/launch-service.ts index 565a4cfc..24d631ac 100644 --- a/packages/core/src/profiles/launch-service.ts +++ b/packages/core/src/profiles/launch-service.ts @@ -14,6 +14,7 @@ import { findRunningOpenCodeAppPid, launchOpenCodeAppProfile, openCodeAppLaunchS import { writeOpenCodeGatewayConfig } from "@ccr/core/agents/opencode/profile-config"; import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime"; import { CONFIGDIR } from "@ccr/core/config/constants"; +import { endpoint } from "@ccr/core/gateway/core-runtime/supervisor"; import { gatewayService } from "@ccr/core/gateway/service"; import { TOOL_HUB_MCP_RUNTIME_FILE_NAME, bundledToolHubMcpEntryPathCandidates } from "@ccr/core/mcp/toolhub-config"; import { mediaToolsGatewayEndpoint } from "@ccr/core/mcp/grok-media-config"; @@ -737,7 +738,7 @@ function isAddressInUseError(message: string | undefined): boolean { function profileGatewayEndpoint(config: AppConfig): string { const host = probeGatewayHost(config.gateway.host); - return `http://${formatEndpointHost(host)}:${config.gateway.port}/`; + return endpoint(host, config.gateway.port); } function probeGatewayHost(host: string): string { @@ -750,10 +751,6 @@ function probeGatewayHost(host: string): string { return host; } -function formatEndpointHost(host: string): string { - return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; -} - function profileGatewayConfigFor(config: AppConfig, profile: ReturnType): AppConfig { const token = findProfileApiKey(config, profile); if (!token) { diff --git a/packages/core/src/web/management-server.ts b/packages/core/src/web/management-server.ts index d91a42c8..b5a1094f 100644 --- a/packages/core/src/web/management-server.ts +++ b/packages/core/src/web/management-server.ts @@ -265,7 +265,17 @@ const rpcHandlers: Record = { const synced = await syncClaudeAppGatewayConfig(baseConfig); const savedConfig = synced.config; let runtimeStatus = gatewayService.getStatus(); - if (synced.configChanged || shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig) || runtimeStatus.state !== "running") { + const restartRequired = synced.configChanged || + shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig) || + runtimeStatus.state !== "running"; + if (runtimeStatus.gatewayManagedExternally) { + if (restartRequired) { + runtimeStatus = await gatewayService.restart(savedConfig); + } else { + await gatewayService.updateConfig(savedConfig); + runtimeStatus = gatewayService.getStatus(); + } + } else if (restartRequired) { runtimeStatus = await gatewayService.start(savedConfig); } else { await gatewayService.updateConfig(savedConfig); @@ -382,14 +392,14 @@ const rpcHandlers: Record = { restartGateway: async () => { const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(await loadAppConfig()); const config = syncedClaudeAppConfig.config; - const status = await gatewayService.start(config); + const status = await gatewayService.restart(config); await applyProfileIfServiceRunning(config, status); return status; }, restartProxy: async () => { const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(await loadAppConfig()); const config = syncedClaudeAppConfig.config; - const status = await gatewayService.start(config); + const status = await gatewayService.restart(config); await applyProfileIfServiceRunning(config, status); return proxyService.getStatus(); }, @@ -420,7 +430,16 @@ const rpcHandlers: Record = { const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(savedConfig); savedConfig = syncedClaudeAppConfig.config; let runtimeStatus = gatewayService.getStatus(); - if (syncedClaudeAppConfig.configChanged || shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig)) { + const restartRequired = syncedClaudeAppConfig.configChanged || + shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig); + if (runtimeStatus.gatewayManagedExternally) { + if (restartRequired) { + runtimeStatus = await gatewayService.restart(savedConfig); + } else { + await gatewayService.updateConfig(savedConfig); + runtimeStatus = gatewayService.getStatus(); + } + } else if (restartRequired) { runtimeStatus = await gatewayService.start(savedConfig); } else { await gatewayService.updateConfig(savedConfig); diff --git a/packages/core/test/integration/gateway/profile-model-allowlist-http.test.mjs b/packages/core/test/integration/gateway/profile-model-allowlist-http.test.mjs index 41c72194..212e7b5a 100644 --- a/packages/core/test/integration/gateway/profile-model-allowlist-http.test.mjs +++ b/packages/core/test/integration/gateway/profile-model-allowlist-http.test.mjs @@ -11,6 +11,8 @@ import { waitForTcpListener } from "../../support/loopback-listener.mjs"; test("gateway HTTP model discovery and request access honor profile model allowlists", async (t) => { const config = createProfileAllowlistHttpTestConfig(); const plugin = new ClaudeCodeRouterPlugin(config); + const runtimeConfigRevision = "a".repeat(64); + const runtimeConfigReloads = []; const status = { coreEndpoint: "http://127.0.0.1:1", endpoint: "http://127.0.0.1:0", @@ -27,10 +29,14 @@ test("gateway HTTP model discovery and request access honor profile model allowl getBrowserAutomationMcpIntegration: () => undefined, getConfig: () => config, getPlugin: () => plugin, + getRuntimeConfigControlStatus: () => ({ revision: runtimeConfigRevision }), getStatus: () => status, handleBillingUsageSync: unsupportedHandler, handleRawTraceSync: unsupportedHandler, proxyRequest: (request, response, path, apiKey) => pipeline.proxyRequest(request, response, path, apiKey), + requestRuntimeConfigReload: (expectedRevision, forceRestart) => { + runtimeConfigReloads.push({ expectedRevision, forceRestart }); + }, replayContextArchive: async () => ({ ok: false, statusCode: 404, message: "not configured" }) }); const server = createServer((request, response) => { @@ -56,6 +62,29 @@ test("gateway HTTP model discovery and request access honor profile model allowl const endpoint = `http://127.0.0.1:${serverPort(server)}`; status.endpoint = endpoint; + const runtimeConfigStatus = await fetchJson(`${endpoint}/__ccr/runtime/config`, { + headers: authHeaders("gateway-key") + }); + assert.equal(runtimeConfigStatus.revision, runtimeConfigRevision); + assert.equal(runtimeConfigStatus.state, "running"); + + const deniedRuntimeConfigStatus = await fetch(`${endpoint}/__ccr/runtime/config`, { + headers: authHeaders("profile-alpha-key") + }); + assert.equal(deniedRuntimeConfigStatus.status, 403); + + const runtimeConfigReload = await fetch(`${endpoint}/__ccr/runtime/config`, { + body: JSON.stringify({ configRevision: runtimeConfigRevision, forceRestart: true }), + headers: { + ...authHeaders("gateway-key"), + "content-type": "application/json" + }, + method: "POST" + }); + assert.equal(runtimeConfigReload.status, 202, await runtimeConfigReload.text()); + await new Promise((resolve) => setImmediate(resolve)); + assert.deepEqual(runtimeConfigReloads, [{ expectedRevision: runtimeConfigRevision, forceRestart: true }]); + const alphaModels = await fetchJson(`${endpoint}/v1/models`, { headers: authHeaders("profile-alpha-key") }); diff --git a/packages/core/test/unit/gateway/gateway-status.test.mjs b/packages/core/test/unit/gateway/gateway-status.test.mjs index f3cec500..6f485130 100644 --- a/packages/core/test/unit/gateway/gateway-status.test.mjs +++ b/packages/core/test/unit/gateway/gateway-status.test.mjs @@ -11,8 +11,16 @@ import { replacePersistedRuntimeState } from "@ccr/core/config/config-repository.ts"; import { createDefaultAppConfig } from "@ccr/core/config/default-config.ts"; +import { endpoint } from "@ccr/core/gateway/core-runtime/supervisor.ts"; import { gatewayService } from "@ccr/core/gateway/service.ts"; +test("gateway endpoints normalize wildcard and IPv6 hosts", () => { + assert.equal(endpoint("0.0.0.0", 3456), "http://127.0.0.1:3456"); + assert.equal(endpoint("::", 3456), "http://[::1]:3456"); + assert.equal(endpoint("::1", 3456), "http://[::1]:3456"); + assert.equal(endpoint("[::1]", 3456), "http://[::1]:3456"); +}); + test("gateway start persists preflight validation failures for status polling", async () => { await gatewayService.stop(); diff --git a/packages/core/test/unit/profiles/profile-gateway-probe.test.mjs b/packages/core/test/unit/profiles/profile-gateway-probe.test.mjs index 0d178102..e9346fe4 100644 --- a/packages/core/test/unit/profiles/profile-gateway-probe.test.mjs +++ b/packages/core/test/unit/profiles/profile-gateway-probe.test.mjs @@ -1,13 +1,16 @@ import assert from "node:assert/strict"; import test from "node:test"; +import { CLAUDE_CODE_AUTH_MODE_ENV } from "@ccr/core/agents/claude-code/auth-mode.ts"; import { createDefaultAppConfig } from "@ccr/core/config/default-config.ts"; import { ensureProfileGateway } from "@ccr/core/profiles/launch-service.ts"; -function claudeProfileConfig() { +function claudeProfileConfig(authMode = "api-key-helper") { const profile = { agent: "claude-code", enabled: true, - env: {}, + env: { + [CLAUDE_CODE_AUTH_MODE_ENV]: authMode + }, id: "claude-main", model: "Provider/model", name: "Claude Main", @@ -99,6 +102,47 @@ test("existing profile gateway falls back to the root identity response", async } }); +test("existing profile gateway rejects WIF profiles when the root identity lacks the token endpoint", async () => { + const previousFetch = globalThis.fetch; + const paths = []; + globalThis.fetch = async (input) => { + const url = new URL(String(input)); + paths.push(url.pathname); + if (url.pathname === "/health") { + return Response.json({ + core: "http://127.0.0.1:3467", + status: "running", + timestamp: "2026-01-01T00:00:00.000Z" + }); + } + if (url.pathname === "/v1/models") { + return Response.json({ data: [], object: "list" }); + } + if (url.pathname === "/") { + return Response.json({ + endpoints: ["GET /v1/models"], + name: "claude-code-router" + }); + } + throw new Error(`Unexpected gateway probe: ${url.pathname}`); + }; + + try { + const { config, profile } = claudeProfileConfig("wif"); + await assert.rejects( + ensureProfileGateway(config, profile, "Local subscription router", { + reuseExisting: true, + startIfMissing: false + }), + /does not advertise the Claude Code WIF token endpoint/ + ); + + assert.deepEqual(paths, ["/health", "/v1/models", "/"]); + } finally { + globalThis.fetch = previousFetch; + } +}); + test("existing profile gateway retries a transient transport failure", async () => { const previousFetch = globalThis.fetch; const paths = []; diff --git a/packages/core/test/unit/web/web-management-server.test.mjs b/packages/core/test/unit/web/web-management-server.test.mjs index cae58feb..3c60f8ca 100644 --- a/packages/core/test/unit/web/web-management-server.test.mjs +++ b/packages/core/test/unit/web/web-management-server.test.mjs @@ -69,6 +69,7 @@ test("web RPC ignores Origin and Referer when the auth token is valid", async () test("startGateway reuses an already healthy CCR gateway on the configured port", async () => { const { saveAppConfig } = await import("@ccr/core/config/config.ts"); const { createDefaultAppConfig } = await import("@ccr/core/config/default-config.ts"); + const { gatewayRuntimeConfigRevision } = await import("@ccr/core/gateway/runtime-config-control.ts"); const { gatewayService } = await import("@ccr/core/gateway/service.ts"); const { startWebManagementServer } = await import("@ccr/core/web/management-server.ts"); await gatewayService.stop(); @@ -86,7 +87,11 @@ test("startGateway reuses an already healthy CCR gateway on the configured port" type: "openai_chat_completions" }]; const savedConfig = await saveAppConfig(config); - const externalGateway = createHealthyCcrGateway(savedConfig.APIKEY); + const externalGatewayState = { + reloadRequests: [], + revision: gatewayRuntimeConfigRevision(savedConfig) + }; + const externalGateway = createHealthyCcrGateway(savedConfig.APIKEY, externalGatewayState); await listen(externalGateway, gatewayPort); const runtime = await startWebManagementServer({ authToken: webAuthToken, @@ -100,10 +105,25 @@ test("startGateway reuses an already healthy CCR gateway on the configured port" assert.equal(payload.ok, true); assert.equal(payload.value.state, "running", payload.value.lastError); - assert.equal(payload.value.endpoint, `http://127.0.0.1:${gatewayPort}/`); + assert.equal(payload.value.endpoint, `http://127.0.0.1:${gatewayPort}`); assert.equal(payload.value.gatewayManagedExternally, true); assert.equal(gatewayService.getStatus().state, "running"); assert.equal(gatewayService.getStatus().gatewayManagedExternally, true); + + const secondStart = await rpc(runtime.url, webAuthToken, "startGateway"); + assert.equal(secondStart.ok, true); + assert.equal(secondStart.value.endpoint, `http://127.0.0.1:${gatewayPort}`); + assert.equal(secondStart.value.gatewayManagedExternally, true); + + const nextConfig = structuredClone(savedConfig); + nextConfig.Providers[0].name = "Updated Test Provider"; + const reloadCountBeforeSave = externalGatewayState.reloadRequests.length; + const saveResult = await rpc(runtime.url, webAuthToken, "saveConfig", [nextConfig, { applyProfile: false }]); + assert.equal(saveResult.ok, true); + assert.equal(externalGatewayState.reloadRequests.length, reloadCountBeforeSave + 1); + assert.equal(externalGatewayState.reloadRequests.at(-1).forceRestart, true); + assert.equal(externalGatewayState.revision, gatewayRuntimeConfigRevision(saveResult.value)); + assert.equal(gatewayService.getStatus().gatewayManagedExternally, true); } finally { await runtime.close(); await gatewayService.stop(); @@ -124,7 +144,7 @@ async function rpc(baseUrl, authToken, method, args = []) { return response.json(); } -function createHealthyCcrGateway(apiKey) { +function createHealthyCcrGateway(apiKey, state) { return createServer((request, response) => { const url = new URL(request.url || "/", "http://127.0.0.1"); if (url.pathname === "/health") { @@ -152,10 +172,40 @@ function createHealthyCcrGateway(apiKey) { sendJson(response, 200, { data: [{ id: "test-model", object: "model" }], object: "list" }); return; } + if (url.pathname === "/__ccr/runtime/config") { + if (request.headers.authorization !== `Bearer ${apiKey}`) { + sendJson(response, 401, { error: { message: "Invalid API key." } }); + return; + } + if (request.method === "GET") { + sendJson(response, 200, { revision: state.revision, state: "running" }); + return; + } + if (request.method === "POST") { + void readJsonRequest(request).then((body) => { + state.reloadRequests.push(body); + state.revision = body.configRevision; + sendJson(response, 202, { + accepted: true, + configRevision: body.configRevision, + restarting: body.forceRestart === true + }); + }); + return; + } + } sendJson(response, 404, { error: { message: "Not found." } }); }); } +async function readJsonRequest(request) { + const chunks = []; + for await (const chunk of request) { + chunks.push(Buffer.from(chunk)); + } + return JSON.parse(Buffer.concat(chunks).toString("utf8")); +} + function sendJson(response, statusCode, payload) { response.writeHead(statusCode, { "content-type": "application/json" }); response.end(JSON.stringify(payload)); diff --git a/packages/electron/src/main/ipc.ts b/packages/electron/src/main/ipc.ts index 2e1df237..0b2e6abe 100644 --- a/packages/electron/src/main/ipc.ts +++ b/packages/electron/src/main/ipc.ts @@ -220,8 +220,18 @@ ipcMain.handle(IPC_CHANNELS.appApplyClaudeAppGateway, async (_event, config?: Ap const synced = await syncClaudeAppGatewayConfig(baseConfig); const savedConfig = synced.config; let runtimeStatus = gatewayService.getStatus(); + const restartRequired = synced.configChanged || + shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig) || + runtimeStatus.state !== "running"; - if (synced.configChanged || shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig) || runtimeStatus.state !== "running") { + if (runtimeStatus.gatewayManagedExternally) { + if (restartRequired) { + runtimeStatus = await gatewayService.restart(savedConfig); + } else { + await gatewayService.updateConfig(savedConfig); + runtimeStatus = gatewayService.getStatus(); + } + } else if (restartRequired) { runtimeStatus = await gatewayService.start(savedConfig); } else { await gatewayService.updateConfig(savedConfig); @@ -323,7 +333,16 @@ ipcMain.handle(IPC_CHANNELS.appSaveConfig, async (_event, config: AppConfig, opt const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(savedConfig); savedConfig = syncedClaudeAppConfig.config; let runtimeStatus = gatewayService.getStatus(); - if (syncedClaudeAppConfig.configChanged || shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig)) { + const restartRequired = syncedClaudeAppConfig.configChanged || + shouldRestartGatewayForRuntimeConfigChange(previousConfig, savedConfig); + if (runtimeStatus.gatewayManagedExternally) { + if (restartRequired) { + runtimeStatus = await gatewayService.restart(savedConfig); + } else { + await gatewayService.updateConfig(savedConfig); + runtimeStatus = gatewayService.getStatus(); + } + } else if (restartRequired) { runtimeStatus = await gatewayService.start(savedConfig); } else { await gatewayService.updateConfig(savedConfig); @@ -360,7 +379,7 @@ ipcMain.handle(IPC_CHANNELS.appSetOnboardingFinished, async () => { ipcMain.handle(IPC_CHANNELS.appRestartGateway, async () => { const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(await loadAppConfig()); const config = syncedClaudeAppConfig.config; - const status = await gatewayService.start(config); + const status = await gatewayService.restart(config); await applyProfileIfServiceRunning(config, status); await builtInBrowserService.syncProxy(config); return status; @@ -391,7 +410,7 @@ ipcMain.handle(IPC_CHANNELS.appShowMainWindow, () => { ipcMain.handle(IPC_CHANNELS.appRestartProxy, async () => { const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(await loadAppConfig()); const config = syncedClaudeAppConfig.config; - const status = await gatewayService.start(config); + const status = await gatewayService.restart(config); await applyProfileIfServiceRunning(config, status); await builtInBrowserService.syncProxy(config); return proxyService.getStatus(); diff --git a/packages/ui/src/pages/home/components/profiles.tsx b/packages/ui/src/pages/home/components/profiles.tsx index 0a6727c3..6d07890e 100644 --- a/packages/ui/src/pages/home/components/profiles.tsx +++ b/packages/ui/src/pages/home/components/profiles.tsx @@ -997,7 +997,7 @@ export function AddProfileForm({ /> {validation.allowedModels ? {t(validation.allowedModels)} : null} - {draft.agent === "codex" && draft.agent !== "workbuddy" ? ( + {draft.agent === "codex" ? (
{t("Show all sessions")} onChange({ showAllSessions })} />