Compare commits
7 Commits
b3c5a4b41b
...
712664ca00
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712664ca00 | ||
|
|
80df025672 | ||
|
|
89d68927c6 | ||
|
|
7b27167af6 | ||
|
|
e81357e1df | ||
|
|
08c99675c6 | ||
|
|
c016b9c2fa |
18
.gitignore
vendored
18
.gitignore
vendored
@@ -54,3 +54,21 @@ runners/codex-runner-backups/
|
||||
.vscode/
|
||||
|
||||
agents-sdk-docs
|
||||
|
||||
# --- merged from upstream 048a89d (python/node/build/secret noise) ---
|
||||
build/
|
||||
.bun-cache/
|
||||
.DS_Store
|
||||
*.egg-info/
|
||||
.env
|
||||
!.env.example
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
*.swp
|
||||
*.tsbuildinfo
|
||||
.venv/
|
||||
|
||||
278
bun.lock
278
bun.lock
@@ -6,8 +6,8 @@
|
||||
"name": "ejclaw",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"cron-parser": "^5.5.0",
|
||||
"discord.js": "^14.18.0",
|
||||
"cron-parser": "^5.6.1",
|
||||
"discord.js": "^14.26.4",
|
||||
"ejclaw-runners-shared": "workspace:*",
|
||||
"lucide-react": "^1.11.0",
|
||||
"pino": "^9.6.0",
|
||||
@@ -16,7 +16,7 @@
|
||||
"react-dom": "^19.2.5",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"yaml": "^2.8.2",
|
||||
"yaml": "^2.9.0",
|
||||
"zod": "^4.3.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"better-sqlite3": "^12.8.0",
|
||||
"eslint": "^10.2.1",
|
||||
"globals": "^17.5.0",
|
||||
@@ -37,8 +37,8 @@
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.7.0",
|
||||
"typescript-eslint": "^8.59.0",
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.0.18",
|
||||
"vite": "^8.1.3",
|
||||
"vitest": "^4.1.9",
|
||||
},
|
||||
},
|
||||
"runners/agent-runner": {
|
||||
@@ -113,76 +113,18 @@
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
|
||||
|
||||
"@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="],
|
||||
"@discordjs/builders": ["@discordjs/builders@1.14.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.40", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ=="],
|
||||
|
||||
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
|
||||
|
||||
"@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
|
||||
|
||||
"@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
|
||||
"@discordjs/rest": ["@discordjs/rest@2.6.3", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.2.0", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.5", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.50", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "^6.27.0" } }, "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg=="],
|
||||
|
||||
"@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
|
||||
|
||||
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
@@ -219,8 +161,6 @@
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||
|
||||
"@openai/codex": ["@openai/codex@0.133.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.133.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.133.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.133.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.133.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.133.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.133.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-Gh42kLLBo/6gpnHmDzUWDVvyS57ekCB1+1Dz0RG2oIl3Lhk1uwrjSj/PwaJWWh4Rw/rUp1RqkwrMugFfFEOlqQ=="],
|
||||
|
||||
"@openai/codex-darwin-arm64": ["@openai/codex@0.133.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W7f8+DckLujnqGlptKCzgJU+ooeHKMuk6KYgMFP6A9asn7YUsGUgJqjiBaX8oNcXO6w/pTbKGRARx1kCNS8lIg=="],
|
||||
@@ -235,104 +175,52 @@
|
||||
|
||||
"@openai/codex-win32-x64": ["@openai/codex@0.133.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-u3ji78DIPZCGJeELuovsAnaZH+vK9gsA4F6M1y+Uy2s80Sz7/i1S0KL81qGReYji3urSjgBpkQuNP47GXOqxrQ=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
|
||||
"@oxc-project/types": ["@oxc-project/types@0.146.0", "", {}, "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA=="],
|
||||
|
||||
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="],
|
||||
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.5", "", { "os": "android", "cpu": "arm" }, "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="],
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.5", "", { "os": "android", "cpu": "arm64" }, "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="],
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="],
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="],
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="],
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.5", "", { "os": "linux", "cpu": "arm" }, "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="],
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="],
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="],
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="],
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="],
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="],
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="],
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.5", "", { "os": "none", "cpu": "arm64" }, "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="],
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="],
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
||||
|
||||
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
||||
|
||||
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
||||
|
||||
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
|
||||
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.5", "", {}, "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ=="],
|
||||
|
||||
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||
@@ -391,21 +279,21 @@
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||
|
||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.18", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.18", "vitest": "4.0.18" }, "optionalPeers": ["@vitest/browser"] }, "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg=="],
|
||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.11", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.11", "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" }, "peerDependencies": { "@vitest/browser": "4.1.11", "vitest": "4.1.11" }, "optionalPeers": ["@vitest/browser"] }, "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
|
||||
"@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="],
|
||||
"@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="],
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="],
|
||||
"@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="],
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
|
||||
"@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
|
||||
"@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="],
|
||||
|
||||
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
|
||||
|
||||
@@ -421,7 +309,7 @@
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.11", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw=="],
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA=="],
|
||||
|
||||
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||
|
||||
@@ -475,13 +363,15 @@
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||
|
||||
"cron-parser": ["cron-parser@5.5.0", "", { "dependencies": { "luxon": "^3.7.1" } }, "sha512-oML4lKUXxizYswqmxuOCpgFS8BNUJpIu6k/2HVHyaL8Ynnf3wdf9tkns0yRdJLSIjkJ+b0DXHMZEHGpMwjnPww=="],
|
||||
"cron-parser": ["cron-parser@5.10.0", "", { "dependencies": { "luxon": "^3.7.2" } }, "sha512-izNAxJyRWUP8ljBoDSub5WyrVOUlT4SLGShswE7eoRBpp6QUsSycYxLBMJlbshgPBMcPT/nrfgjNY2918ayv2A=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
@@ -507,9 +397,9 @@
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"discord-api-types": ["discord-api-types@0.38.41", "", {}, "sha512-yMECyR8j9c2fVTvCQ+Qc24pweYFIZk/XoxDOmt1UvPeSw5tK6gXBd/2hhP+FEAe9Y6ny8pRMaf618XDK4U53OQ=="],
|
||||
"discord-api-types": ["discord-api-types@0.38.53", "", {}, "sha512-HL1zz/UuZ+bbJjA/X8Kbxx9gk8v9rJAbTeWRNYKmIdjwJ7EovjlHgoJTxcLpATfNJ+AonOtMdy3Y5MVIJAAd/A=="],
|
||||
|
||||
"discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="],
|
||||
"discord.js": ["discord.js@14.27.0", "", { "dependencies": { "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.2", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.5", "discord-api-types": "^0.38.49", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "^6.27.0" } }, "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
@@ -529,12 +419,10 @@
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
"es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": "bin/esbuild" }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
@@ -617,8 +505,6 @@
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
@@ -711,29 +597,29 @@
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
@@ -863,7 +749,7 @@
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
||||
|
||||
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
||||
|
||||
@@ -905,7 +791,7 @@
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": "bin.js" }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
|
||||
|
||||
@@ -921,7 +807,7 @@
|
||||
|
||||
"playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="],
|
||||
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
|
||||
|
||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||
|
||||
@@ -969,11 +855,7 @@
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@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" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="],
|
||||
|
||||
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
||||
"rolldown": ["rolldown@1.2.5", "", { "dependencies": { "@oxc-project/types": "=0.146.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.5", "@rolldown/binding-android-arm64": "1.2.5", "@rolldown/binding-darwin-arm64": "1.2.5", "@rolldown/binding-darwin-x64": "1.2.5", "@rolldown/binding-freebsd-x64": "1.2.5", "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", "@rolldown/binding-linux-arm64-gnu": "1.2.5", "@rolldown/binding-linux-arm64-musl": "1.2.5", "@rolldown/binding-linux-ppc64-gnu": "1.2.5", "@rolldown/binding-linux-s390x-gnu": "1.2.5", "@rolldown/binding-linux-x64-gnu": "1.2.5", "@rolldown/binding-linux-x64-musl": "1.2.5", "@rolldown/binding-openharmony-arm64": "1.2.5", "@rolldown/binding-win32-arm64-msvc": "1.2.5", "@rolldown/binding-win32-x64-msvc": "1.2.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
@@ -1027,7 +909,7 @@
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
"std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
@@ -1051,9 +933,9 @@
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
|
||||
"tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
@@ -1069,8 +951,6 @@
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
|
||||
|
||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
@@ -1081,7 +961,7 @@
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.59.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.0", "@typescript-eslint/parser": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw=="],
|
||||
|
||||
"undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
|
||||
"undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
@@ -1109,9 +989,9 @@
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="],
|
||||
"vite": ["vite@8.2.2", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.26", "rolldown": "~1.2.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q=="],
|
||||
|
||||
"vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="],
|
||||
"vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
@@ -1123,7 +1003,7 @@
|
||||
|
||||
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": "bin.mjs" }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
@@ -1133,20 +1013,32 @@
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@discordjs/formatters/discord-api-types": ["discord-api-types@0.38.41", "", {}, "sha512-yMECyR8j9c2fVTvCQ+Qc24pweYFIZk/XoxDOmt1UvPeSw5tK6gXBd/2hhP+FEAe9Y6ny8pRMaf618XDK4U53OQ=="],
|
||||
|
||||
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@discordjs/util/discord-api-types": ["discord-api-types@0.38.41", "", {}, "sha512-yMECyR8j9c2fVTvCQ+Qc24pweYFIZk/XoxDOmt1UvPeSw5tK6gXBd/2hhP+FEAe9Y6ny8pRMaf618XDK4U53OQ=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
|
||||
|
||||
"@discordjs/ws/discord-api-types": ["discord-api-types@0.38.41", "", {}, "sha512-yMECyR8j9c2fVTvCQ+Qc24pweYFIZk/XoxDOmt1UvPeSw5tK6gXBd/2hhP+FEAe9Y6ny8pRMaf618XDK4U53OQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"ejclaw-agent-runner/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
|
||||
|
||||
"ejclaw-agent-runner/cron-parser": ["cron-parser@5.5.0", "", { "dependencies": { "luxon": "^3.7.1" } }, "sha512-oML4lKUXxizYswqmxuOCpgFS8BNUJpIu6k/2HVHyaL8Ynnf3wdf9tkns0yRdJLSIjkJ+b0DXHMZEHGpMwjnPww=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
@@ -1155,30 +1047,26 @@
|
||||
|
||||
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="],
|
||||
|
||||
"rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"vitest/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
"vitest/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"vitest/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
"vitest/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"vitest/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": "bin/vite.js" }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||
"@discordjs/ws/@discordjs/rest/@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/rest/undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"ejclaw-agent-runner/@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"vitest/vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"vitest/vite/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"ejclaw-agent-runner/@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
}
|
||||
}
|
||||
|
||||
105
claude_relogin.sh
Executable file
105
claude_relogin.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# claude_relogin.sh — 이 폴더의 Claude 계정을 OAuth로 재로그인한다.
|
||||
#
|
||||
# 사용법:
|
||||
# ./claude_relogin.sh
|
||||
#
|
||||
# 동작:
|
||||
# 1) 이 스크립트가 있는 폴더의 .env 에서 CLAUDE_CREDENTIALS_PATH 를 읽어온다.
|
||||
# 2) 인증(authorize) URL 을 출력한다 → 브라우저로 열어 로그인한다.
|
||||
# 3) 로그인 완료 후 화면에 나오는 코드를 붙여넣는다.
|
||||
# 4) 코드를 토큰으로 교환해 credentials 파일에 저장한다.
|
||||
# 5) 해당 systemd(--user) 서비스를 재시작한다.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# --- 이 스크립트가 있는 폴더로 이동 ---------------------------------------
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR"
|
||||
|
||||
# --- bun 실행 파일 찾기 ----------------------------------------------------
|
||||
BUN="$(command -v bun 2>/dev/null || true)"
|
||||
[ -z "$BUN" ] && [ -x "$HOME/.bun/bin/bun" ] && BUN="$HOME/.bun/bin/bun"
|
||||
if [ -z "$BUN" ]; then
|
||||
echo "❌ bun 을 찾을 수 없습니다. PATH 를 확인하세요." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- .env 에서 CLAUDE_CREDENTIALS_PATH 읽기 --------------------------------
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ $DIR/.env 가 없습니다." >&2
|
||||
exit 1
|
||||
fi
|
||||
CRED_PATH="$(grep -E '^CLAUDE_CREDENTIALS_PATH=' .env | tail -n1 | cut -d= -f2- | tr -d '"'"'"'' )"
|
||||
if [ -z "${CRED_PATH:-}" ]; then
|
||||
echo "❌ .env 에 CLAUDE_CREDENTIALS_PATH 가 설정되어 있지 않습니다." >&2
|
||||
exit 1
|
||||
fi
|
||||
export CLAUDE_CREDENTIALS_PATH="$CRED_PATH"
|
||||
|
||||
# --- 서비스명 자동 감지 (폴더명 소문자 + .service) -------------------------
|
||||
SERVICE="$(basename "$DIR" | tr '[:upper:]' '[:lower:]').service"
|
||||
|
||||
echo "======================================================================"
|
||||
echo " Claude 재로그인"
|
||||
echo " 폴더 : $DIR"
|
||||
echo " credentials : $CLAUDE_CREDENTIALS_PATH"
|
||||
echo " 서비스 : $SERVICE"
|
||||
echo "======================================================================"
|
||||
echo
|
||||
|
||||
# --- 1단계: authorize URL 발급 --------------------------------------------
|
||||
echo "▶ 1단계: 로그인 URL 생성 중..."
|
||||
PHASE1_OUT="$("$BUN" setup/index.ts --step login 2>/dev/null)"
|
||||
URL="$(printf '%s\n' "$PHASE1_OUT" | grep -E '^AUTHORIZE_URL=' | head -n1 | sed 's/^AUTHORIZE_URL=//')"
|
||||
if [ -z "$URL" ]; then
|
||||
echo "❌ 인증 URL 생성 실패. 출력:" >&2
|
||||
printf '%s\n' "$PHASE1_OUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "▼ 아래 URL 을 브라우저에서 열어 로그인하세요:"
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo "$URL"
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo
|
||||
echo "로그인 완료 후 화면에 표시되는 코드를 복사하세요."
|
||||
echo "(코드는 'xxxx' 또는 'xxxx#yyyy' 형태 모두 가능합니다.)"
|
||||
echo
|
||||
|
||||
# --- 코드 입력 받기 --------------------------------------------------------
|
||||
printf "붙여넣을 코드: "
|
||||
read -r CODE
|
||||
CODE="$(printf '%s' "$CODE" | tr -d '[:space:]')"
|
||||
if [ -z "$CODE" ]; then
|
||||
echo "❌ 코드가 비어 있습니다. 중단합니다." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 2단계: 코드 → 토큰 교환 ----------------------------------------------
|
||||
echo
|
||||
echo "▶ 2단계: 코드 교환 및 credentials 저장 중..."
|
||||
if "$BUN" setup/index.ts --step login --code "$CODE"; then
|
||||
echo "✅ 로그인 성공 — credentials 저장 완료: $CLAUDE_CREDENTIALS_PATH"
|
||||
else
|
||||
echo "❌ 코드 교환 실패. 코드가 만료되었거나 잘못되었을 수 있습니다." >&2
|
||||
echo " 스크립트를 다시 실행해 새 URL 로 시도하세요." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 3단계: 서비스 재시작 --------------------------------------------------
|
||||
echo
|
||||
echo "▶ 3단계: $SERVICE 재시작..."
|
||||
if systemctl --user restart "$SERVICE" 2>/dev/null; then
|
||||
sleep 1
|
||||
STATE="$(systemctl --user is-active "$SERVICE" 2>/dev/null || true)"
|
||||
echo "✅ $SERVICE 상태: $STATE"
|
||||
else
|
||||
echo "⚠ $SERVICE 재시작을 건너뜀(서비스가 없거나 권한 없음). 수동으로 재시작하세요:" >&2
|
||||
echo " systemctl --user restart $SERVICE" >&2
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "완료. 🎉"
|
||||
95
codex_relogin.sh
Executable file
95
codex_relogin.sh
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# codex_relogin.sh — Codex(ChatGPT) 계정을 device-code OAuth 로 재로그인한다.
|
||||
#
|
||||
# 사용법:
|
||||
# ./codex_relogin.sh # 기본 슬롯(0) 재로그인
|
||||
# ./codex_relogin.sh 1 # ~/.codex-accounts/1 슬롯 재로그인
|
||||
#
|
||||
# 참고:
|
||||
# * Codex 로그인은 이 호스트에서 두 봇(EJClaw / KAClaw)이 "공유"한다.
|
||||
# 계정 저장 위치가 폴더별이 아니라 ~/.codex-accounts/{n} 이기 때문.
|
||||
# → 한 번만 재로그인하면 두 봇 모두에 반영된다.
|
||||
# * codex CLI 가 URL + 일회용 코드를 출력한다(15분 유효). 브라우저에서
|
||||
# 로그인/코드입력을 마치면 codex 가 auth.json 을 자동 저장한다.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR"
|
||||
|
||||
# --- codex 실행 파일 찾기 --------------------------------------------------
|
||||
CODEX="$(command -v codex 2>/dev/null || true)"
|
||||
if [ -z "$CODEX" ]; then
|
||||
# fnm 로 설치된 node 전역 bin 에서 탐색
|
||||
for c in "$HOME"/.local/share/fnm/node-versions/*/installation/bin/codex; do
|
||||
[ -x "$c" ] && CODEX="$c" && break
|
||||
done
|
||||
fi
|
||||
if [ -z "$CODEX" ]; then
|
||||
echo "❌ codex CLI 를 찾을 수 없습니다. 'command -v codex' 로 확인하세요." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 대상 슬롯 결정 --------------------------------------------------------
|
||||
# 앱은 ~/.codex-accounts/{n}/auth.json 를 읽는다. 없으면 ~/.codex 로 폴백.
|
||||
SLOT="${1:-0}"
|
||||
ACCOUNTS_DIR="$HOME/.codex-accounts"
|
||||
if [ -d "$ACCOUNTS_DIR/$SLOT" ]; then
|
||||
CODEX_TARGET="$ACCOUNTS_DIR/$SLOT"
|
||||
elif [ -d "$ACCOUNTS_DIR" ] && ls -d "$ACCOUNTS_DIR"/[0-9]* >/dev/null 2>&1; then
|
||||
echo "❌ 슬롯 '$SLOT' 이(가) 없습니다. 사용 가능한 슬롯:" >&2
|
||||
ls -d "$ACCOUNTS_DIR"/[0-9]* 2>/dev/null | sed 's#.*/# - #' >&2
|
||||
exit 1
|
||||
else
|
||||
# accounts 디렉터리가 비어있으면 기본 codex home 사용
|
||||
CODEX_TARGET="$HOME/.codex"
|
||||
fi
|
||||
export CODEX_HOME="$CODEX_TARGET"
|
||||
|
||||
# --- 서비스명 자동 감지 (폴더명 소문자 + .service) -------------------------
|
||||
SERVICE="$(basename "$DIR" | tr '[:upper:]' '[:lower:]').service"
|
||||
|
||||
echo "======================================================================"
|
||||
echo " Codex 재로그인 (device-code OAuth)"
|
||||
echo " codex : $CODEX"
|
||||
echo " CODEX_HOME : $CODEX_HOME (auth.json 저장 위치)"
|
||||
echo " 서비스 : $SERVICE"
|
||||
echo " ※ Codex 로그인은 EJClaw/KAClaw 두 봇이 공유합니다."
|
||||
echo "======================================================================"
|
||||
echo
|
||||
echo "▶ 아래 안내대로 브라우저에서 URL 을 열고 표시되는 코드를 입력하세요."
|
||||
echo " (로그인이 끝나면 자동으로 저장되고 이 창이 넘어갑니다.)"
|
||||
echo "----------------------------------------------------------------------"
|
||||
|
||||
# --- 로그인 실행 (codex 가 URL/코드 출력 + 폴링까지 담당) ------------------
|
||||
if "$CODEX" login --device-auth; then
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo "✅ Codex 로그인 성공 — auth.json 저장: $CODEX_HOME/auth.json"
|
||||
else
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo "❌ Codex 로그인 실패 또는 취소됨. 다시 실행해 시도하세요." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 상태 확인 -------------------------------------------------------------
|
||||
echo
|
||||
echo "▶ 로그인 상태 확인:"
|
||||
"$CODEX" login status 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# --- 서비스 재시작 ---------------------------------------------------------
|
||||
echo
|
||||
echo "▶ $SERVICE 재시작..."
|
||||
if systemctl --user restart "$SERVICE" 2>/dev/null; then
|
||||
sleep 1
|
||||
STATE="$(systemctl --user is-active "$SERVICE" 2>/dev/null || true)"
|
||||
echo "✅ $SERVICE 상태: $STATE"
|
||||
echo " (공유 계정이므로 다른 봇도 새 로그인을 사용합니다. 필요하면"
|
||||
echo " 상대 봇도 재시작하세요: systemctl --user restart <다른서비스>)"
|
||||
else
|
||||
echo "⚠ $SERVICE 재시작 건너뜀(서비스 없음/권한 없음). 수동 재시작:" >&2
|
||||
echo " systemctl --user restart $SERVICE" >&2
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "완료. 🎉"
|
||||
12
package.json
12
package.json
@@ -41,8 +41,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"cron-parser": "^5.5.0",
|
||||
"discord.js": "^14.18.0",
|
||||
"cron-parser": "^5.6.1",
|
||||
"discord.js": "^14.26.4",
|
||||
"ejclaw-runners-shared": "workspace:*",
|
||||
"lucide-react": "^1.11.0",
|
||||
"pino": "^9.6.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"react-dom": "^19.2.5",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"yaml": "^2.8.2",
|
||||
"yaml": "^2.9.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -63,7 +63,7 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"better-sqlite3": "^12.8.0",
|
||||
"eslint": "^10.2.1",
|
||||
"globals": "^17.5.0",
|
||||
@@ -72,8 +72,8 @@
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.7.0",
|
||||
"typescript-eslint": "^8.59.0",
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.0.18"
|
||||
"vite": "^8.1.3",
|
||||
"vitest": "^4.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
||||
@@ -43,3 +43,8 @@ You may receive reference opinions from external models appended to your prompt.
|
||||
- If both sides are saying the same thing but not acting on it, call it out and direct the owner to act
|
||||
- If the conversation shows the owner asking the user a question (not the reviewer), always ESCALATE — the arbiter cannot answer on behalf of the user
|
||||
- If you see a prior arbiter verdict of PROCEED in the history but the same issue persists, do NOT repeat PROCEED — use ESCALATE instead
|
||||
|
||||
## Language
|
||||
|
||||
- Write your verdict in the user's language (Korean for this deployment) unless the user wrote in another language. The user must be able to read your verdict.
|
||||
- Keep ONLY the leading verdict keyword in English — `PROCEED` / `REVISE` / `RESET` / `ESCALATE` — exactly as specified above, because the system parses that first token. Write everything after it (reasoning, evidence, the required action for the owner) in Korean.
|
||||
|
||||
@@ -5,6 +5,18 @@ Use it to acknowledge a request before starting longer work.
|
||||
|
||||
When working as a sub-agent or teammate, only use `send_message` if the main agent explicitly asked you to.
|
||||
|
||||
## Background work never notifies you — finish within the turn
|
||||
|
||||
Nothing in this environment re-invokes you when a background process, a background shell command, or a `run_in_background` job finishes. You will NOT be automatically notified when a build, compile, download, deploy, or any background task completes. The built-in tool descriptions that say "you'll be notified when it finishes" do NOT apply here.
|
||||
|
||||
If you end your turn with a sentence like "I'll wait for the build to complete — I'll be notified", the turn simply ends and nothing ever resumes — the user is left stuck staring at that message. This is a real, repeated failure. Do not do it.
|
||||
|
||||
Therefore:
|
||||
|
||||
- Do not launch long work in the background and then end the turn expecting a callback. Run it in the foreground and block/poll until it actually finishes inside this same turn, then report the real result.
|
||||
- If something genuinely cannot complete within one turn, do not pretend a notification is coming. Finish with a concrete status and explicitly ask the user to send a follow-up message to continue. For recurring polling of CI/scheduled work, use the `schedule_task` / `watch_ci` tools, which DO re-invoke you.
|
||||
- Never leave an intermediate "I'll wait … I'll be notified" planning sentence as your final answer.
|
||||
|
||||
## Media attachments
|
||||
|
||||
When a locally generated image, screenshot, video, audio, or document should appear in Discord, include a `MEDIA:` directive on its own line with an absolute local path:
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* builds (captured from the official CLI). This is the manual / paste-code
|
||||
* variant: the user visits the authorize URL, completes login in their
|
||||
* browser, and pastes the code back. We exchange it for tokens and write
|
||||
* `~/.claude/.credentials.json`.
|
||||
* `CLAUDE_CREDENTIALS_PATH` or `~/.claude/.credentials.json`.
|
||||
*
|
||||
* Usage:
|
||||
* bun setup/index.ts --step login # phase 1: print authorize URL
|
||||
* bun setup/index.ts --step login --code <c> # phase 2: exchange the code
|
||||
*
|
||||
* Phase 1 stashes the PKCE verifier + state in /tmp/ejclaw-claude-login.json
|
||||
* Phase 1 stashes the PKCE verifier + state in a /tmp login state file
|
||||
* (mode 0600). Phase 2 reads it back, exchanges the code, writes credentials.
|
||||
*
|
||||
* This step is run by hand for re-auth. Once `.credentials.json` exists, the
|
||||
@@ -39,8 +39,21 @@ const SCOPES = [
|
||||
'user:file_upload',
|
||||
];
|
||||
|
||||
const STATE_FILE = path.join(os.tmpdir(), 'ejclaw-claude-login.json');
|
||||
const CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
||||
function credentialsPath(): string {
|
||||
const configured = process.env.CLAUDE_CREDENTIALS_PATH?.trim();
|
||||
return configured
|
||||
? path.resolve(configured)
|
||||
: path.join(os.homedir(), '.claude', '.credentials.json');
|
||||
}
|
||||
|
||||
function stateFilePath(): string {
|
||||
const hash = crypto
|
||||
.createHash('sha256')
|
||||
.update(credentialsPath())
|
||||
.digest('hex')
|
||||
.slice(0, 12);
|
||||
return path.join(os.tmpdir(), `ejclaw-claude-login-${hash}.json`);
|
||||
}
|
||||
|
||||
interface PendingState {
|
||||
verifier: string;
|
||||
@@ -90,13 +103,14 @@ function buildAuthorizeUrl(state: string, challenge: string): string {
|
||||
}
|
||||
|
||||
function writePendingState(p: PendingState): void {
|
||||
fs.writeFileSync(STATE_FILE, JSON.stringify(p), { mode: 0o600 });
|
||||
fs.writeFileSync(stateFilePath(), JSON.stringify(p), { mode: 0o600 });
|
||||
}
|
||||
|
||||
function readPendingState(): PendingState | null {
|
||||
if (!fs.existsSync(STATE_FILE)) return null;
|
||||
const stateFile = stateFilePath();
|
||||
if (!fs.existsSync(stateFile)) return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as PendingState;
|
||||
return JSON.parse(fs.readFileSync(stateFile, 'utf-8')) as PendingState;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -136,6 +150,7 @@ async function exchangeCode(
|
||||
}
|
||||
|
||||
function writeCredentials(resp: ExchangeResponse): void {
|
||||
const credsPath = credentialsPath();
|
||||
const expiresAt = Date.now() + resp.expires_in * 1000;
|
||||
const creds = {
|
||||
claudeAiOauth: {
|
||||
@@ -150,11 +165,11 @@ function writeCredentials(resp: ExchangeResponse): void {
|
||||
: '',
|
||||
},
|
||||
};
|
||||
const dir = path.dirname(CREDS_PATH);
|
||||
const dir = path.dirname(credsPath);
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
const tmp = `${CREDS_PATH}.tmp`;
|
||||
const tmp = `${credsPath}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
||||
fs.renameSync(tmp, CREDS_PATH);
|
||||
fs.renameSync(tmp, credsPath);
|
||||
}
|
||||
|
||||
interface Args {
|
||||
@@ -208,15 +223,15 @@ export async function run(args: string[]): Promise<void> {
|
||||
try {
|
||||
const resp = await exchangeCode(code, pending.verifier, pending.state);
|
||||
writeCredentials(resp);
|
||||
fs.unlinkSync(STATE_FILE);
|
||||
fs.unlinkSync(stateFilePath());
|
||||
const newScopes = (resp.scope || SCOPES.join(' ')).split(' ').sort();
|
||||
logger.info(
|
||||
{ scopes: newScopes, expiresInMin: Math.round(resp.expires_in / 60) },
|
||||
'Wrote ~/.claude/.credentials.json',
|
||||
'Wrote Claude credentials',
|
||||
);
|
||||
emitStatus('LOGIN', {
|
||||
STATUS: 'success',
|
||||
CREDENTIALS_PATH: CREDS_PATH,
|
||||
CREDENTIALS_PATH: credentialsPath(),
|
||||
SCOPES: newScopes.join(','),
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('migrate room registrations step', () => {
|
||||
chat_jid: 'dc:legacy-room',
|
||||
room_mode: 'tribunal',
|
||||
mode_source: 'inferred',
|
||||
owner_agent_type: 'codex',
|
||||
owner_agent_type: 'claude-code',
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
@@ -141,12 +141,12 @@ describe('migrate room registrations step', () => {
|
||||
{
|
||||
chat_jid: 'dc:legacy-room',
|
||||
role: 'owner',
|
||||
agent_type: 'codex',
|
||||
agent_type: 'claude-code',
|
||||
},
|
||||
{
|
||||
chat_jid: 'dc:legacy-room',
|
||||
role: 'reviewer',
|
||||
agent_type: 'claude-code',
|
||||
agent_type: 'codex',
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
|
||||
@@ -28,6 +28,19 @@ vi.mock('../src/group-folder.js', () => ({
|
||||
isValidGroupFolder: isValidGroupFolderMock,
|
||||
}));
|
||||
|
||||
// Registration now provisions the paired workspace (which shells out to git)
|
||||
// and hot-reloads the running service. Stub both so the unit test stays hermetic
|
||||
// and only verifies the registration delegation, not workspace/git side effects.
|
||||
vi.mock('../src/paired-workspace-manager.js', () => ({
|
||||
ensurePairedWorkspaceProvisioned: vi.fn(
|
||||
() => '/tmp/ejclaw-groups/test-room/owner',
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../src/runtime-reload-signal.js', () => ({
|
||||
signalEjclawReload: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock('../src/logger.js', () => ({
|
||||
logger: {
|
||||
info: loggerInfoMock,
|
||||
@@ -84,6 +97,7 @@ describe('register step', () => {
|
||||
FOLDER: 'test-room',
|
||||
CHANNEL: 'discord',
|
||||
STATUS: 'success',
|
||||
RELOADED: 'no',
|
||||
LOG: 'logs/setup.log',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from './codex-token-rotation.js';
|
||||
import { readCodexFeatureFromFile } from './codex-config-features.js';
|
||||
import { ensureClaudeSessionSettings } from './claude-session-settings.js';
|
||||
import { getClaudeCredentialsPath } from './claude-credentials-path.js';
|
||||
import {
|
||||
getConfiguredClaudeTokens,
|
||||
getCurrentToken,
|
||||
@@ -53,16 +54,35 @@ function syncDirectoryEntries(sources: string[], destination: string): void {
|
||||
for (const entry of fs.readdirSync(source)) {
|
||||
const srcPath = path.join(source, entry);
|
||||
const dstPath = path.join(destination, entry);
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
if (fs.statSync(srcPath).isDirectory()) {
|
||||
fs.cpSync(srcPath, dstPath, { recursive: true });
|
||||
} else {
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
fs.copyFileSync(srcPath, dstPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncClaudeCredentialsToSessionDir(sessionClaudeDir: string): void {
|
||||
// The agent spawns with CLAUDE_CONFIG_DIR=<sessionClaudeDir>. Without this
|
||||
// pre-sync, a freshly created session dir has no credentials.json and the
|
||||
// child Claude process fails to authenticate on its first run. token-
|
||||
// refresh.ts's syncToSessionDirs only kicks in on the next refresh cycle.
|
||||
const srcPath = getClaudeCredentialsPath(0, { allowHomeFallback: true });
|
||||
if (!srcPath || !fs.existsSync(srcPath)) return;
|
||||
const dest = path.join(sessionClaudeDir, '.credentials.json');
|
||||
try {
|
||||
fs.copyFileSync(srcPath, dest);
|
||||
fs.chmodSync(dest, 0o600);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ err, srcPath, dest },
|
||||
'Failed to pre-sync Claude credentials to session dir',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type SkillSyncScope = 'codex-user' | 'claude-user' | 'runner' | 'workdir';
|
||||
|
||||
interface SkillSyncSource {
|
||||
@@ -595,6 +615,7 @@ export function prepareGroupEnvironment(
|
||||
const groupSessionsDir = path.join(sessionRootDir, '.claude');
|
||||
fs.mkdirSync(groupSessionsDir, { recursive: true });
|
||||
ensureClaudeSessionSettings(groupSessionsDir);
|
||||
syncClaudeCredentialsToSessionDir(groupSessionsDir);
|
||||
|
||||
const workDirClaude = group.workDir
|
||||
? path.join(group.workDir, '.claude')
|
||||
|
||||
@@ -1050,6 +1050,57 @@ describe('sendMessage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// --- editMessage ---
|
||||
|
||||
describe('editMessage', () => {
|
||||
it('edits in place when text is within the 2000 char limit', async () => {
|
||||
const opts = createTestOpts();
|
||||
const channel = new DiscordChannel('test-token', opts);
|
||||
await channel.connect();
|
||||
|
||||
const mockMsg = { edit: vi.fn().mockResolvedValue(undefined) };
|
||||
const mockChannel = {
|
||||
messages: { fetch: vi.fn().mockResolvedValue(mockMsg) },
|
||||
send: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
currentClient().channels.fetch.mockResolvedValue(mockChannel);
|
||||
|
||||
await channel.editMessage('dc:1234567890123456', 'msg-1', 'short final');
|
||||
|
||||
expect(mockMsg.edit).toHaveBeenCalledWith('short final');
|
||||
expect(mockChannel.send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('edits the first chunk and sends the remainder when text exceeds 2000 chars', async () => {
|
||||
const opts = createTestOpts();
|
||||
const channel = new DiscordChannel('test-token', opts);
|
||||
await channel.connect();
|
||||
|
||||
const mockMsg = { edit: vi.fn().mockResolvedValue(undefined) };
|
||||
const mockChannel = {
|
||||
messages: { fetch: vi.fn().mockResolvedValue(mockMsg) },
|
||||
send: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
currentClient().channels.fetch.mockResolvedValue(mockChannel);
|
||||
|
||||
const longText = 'y'.repeat(4500);
|
||||
await channel.editMessage('dc:1234567890123456', 'msg-1', longText);
|
||||
|
||||
// First 2000 chars replace the tracked message, remainder as follow-ups.
|
||||
expect(mockMsg.edit).toHaveBeenCalledTimes(1);
|
||||
expect(mockMsg.edit).toHaveBeenCalledWith('y'.repeat(2000));
|
||||
expect(mockChannel.send).toHaveBeenCalledTimes(2);
|
||||
expect(mockChannel.send).toHaveBeenNthCalledWith(1, {
|
||||
content: 'y'.repeat(2000),
|
||||
flags: 1 << 2,
|
||||
});
|
||||
expect(mockChannel.send).toHaveBeenNthCalledWith(2, {
|
||||
content: 'y'.repeat(500),
|
||||
flags: 1 << 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- ownsJid ---
|
||||
|
||||
describe('ownsJid', () => {
|
||||
@@ -1159,4 +1210,50 @@ describe('channel properties', () => {
|
||||
const channel = new DiscordChannel('test-token', createTestOpts());
|
||||
expect(channel.name).toBe('discord');
|
||||
});
|
||||
|
||||
it('fetches missing channel metadata individually after a guild batch', async () => {
|
||||
const channel = new DiscordChannel('test-token', createTestOpts());
|
||||
await channel.connect();
|
||||
|
||||
const firstGuild = {
|
||||
channels: {
|
||||
fetch: vi.fn().mockResolvedValue(
|
||||
new Map([
|
||||
[
|
||||
'111',
|
||||
{
|
||||
name: 'first-chat',
|
||||
position: 1,
|
||||
parent: { name: 'First Guild', position: 1 },
|
||||
},
|
||||
],
|
||||
]),
|
||||
),
|
||||
},
|
||||
};
|
||||
currentClient().channels.fetch.mockImplementation(async (id: string) => {
|
||||
if (id === '111') {
|
||||
return {
|
||||
name: 'first-chat',
|
||||
position: 1,
|
||||
parent: { name: 'First Guild', position: 1 },
|
||||
guild: firstGuild,
|
||||
};
|
||||
}
|
||||
if (id === '222') {
|
||||
return {
|
||||
name: 'second-chat',
|
||||
position: 2,
|
||||
parent: { name: 'Second Guild', position: 2 },
|
||||
guild: { channels: { fetch: vi.fn() } },
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const metadata = await channel.getChannelMeta(['dc:111', 'dc:222']);
|
||||
|
||||
expect(metadata.get('dc:111')?.name).toBe('first-chat');
|
||||
expect(metadata.get('dc:222')?.name).toBe('second-chat');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -675,6 +675,21 @@ export class DiscordChannel implements Channel {
|
||||
categoryPosition: channel.parent?.position ?? 999,
|
||||
});
|
||||
}
|
||||
|
||||
for (const jid of dcJids) {
|
||||
if (result.has(jid)) continue;
|
||||
const channelId = jid.replace(/^dc:/, '');
|
||||
const channel = await this.client.channels.fetch(channelId);
|
||||
if (channel && 'position' in channel) {
|
||||
const tc = channel as TextChannel;
|
||||
result.set(jid, {
|
||||
name: tc.name,
|
||||
position: tc.position,
|
||||
category: tc.parent?.name || '',
|
||||
categoryPosition: tc.parent?.position ?? 999,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fallback: individual fetches
|
||||
for (const jid of dcJids) {
|
||||
@@ -769,7 +784,23 @@ export class DiscordChannel implements Channel {
|
||||
throw new Error(`Discord channel not found or not editable: ${jid}`);
|
||||
}
|
||||
const msg = await (channel as TextChannel).messages.fetch(messageId);
|
||||
await msg.edit(text);
|
||||
// Discord caps a single message (edits included) at 2000 chars. A long
|
||||
// final answer edited straight in is rejected with
|
||||
// content[BASE_TYPE_MAX_LENGTH]; edit the tracked message with the first
|
||||
// chunk and send the remainder as follow-up messages so nothing is
|
||||
// dropped and no failed-edit fallback is needed.
|
||||
const MAX_LENGTH = 2000;
|
||||
if (text.length <= MAX_LENGTH) {
|
||||
await msg.edit(text);
|
||||
} else {
|
||||
await msg.edit(text.slice(0, MAX_LENGTH));
|
||||
for (let i = MAX_LENGTH; i < text.length; i += MAX_LENGTH) {
|
||||
await (channel as TextChannel).send({
|
||||
content: text.slice(i, i + MAX_LENGTH),
|
||||
flags: MessageFlags.SuppressEmbeds,
|
||||
});
|
||||
}
|
||||
}
|
||||
logger.info(
|
||||
{
|
||||
jid,
|
||||
|
||||
40
src/claude-credentials-path.ts
Normal file
40
src/claude-credentials-path.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
function readPathEnv(key: string): string | null {
|
||||
const value = process.env[key]?.trim();
|
||||
return value ? path.resolve(value) : null;
|
||||
}
|
||||
|
||||
export function getClaudeCredentialsPath(
|
||||
accountIndex: number,
|
||||
options: { allowHomeFallback?: boolean } = {},
|
||||
): string | null {
|
||||
const explicitPath = readPathEnv('CLAUDE_CREDENTIALS_PATH');
|
||||
if (accountIndex === 0 && explicitPath) return explicitPath;
|
||||
|
||||
const explicitAccountsDir = readPathEnv('CLAUDE_ACCOUNTS_DIR');
|
||||
if (accountIndex > 0 && explicitAccountsDir) {
|
||||
return path.join(
|
||||
explicitAccountsDir,
|
||||
String(accountIndex),
|
||||
'.credentials.json',
|
||||
);
|
||||
}
|
||||
|
||||
if (options.allowHomeFallback === false) return null;
|
||||
|
||||
if (accountIndex === 0) {
|
||||
return path.join(os.homedir(), '.claude', '.credentials.json');
|
||||
}
|
||||
return path.join(
|
||||
os.homedir(),
|
||||
'.claude-accounts',
|
||||
String(accountIndex),
|
||||
'.credentials.json',
|
||||
);
|
||||
}
|
||||
|
||||
export function hasExplicitClaudeCredentialsPath(): boolean {
|
||||
return Boolean(readPathEnv('CLAUDE_CREDENTIALS_PATH'));
|
||||
}
|
||||
@@ -102,6 +102,32 @@ describe('Claude usage 429 Retry-After backoff', () => {
|
||||
expect(second[0].usageRateLimited).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to the default cooldown when Retry-After is 0 (does not disable backoff)', async () => {
|
||||
// Production regression: the usage endpoint was returning 429 with
|
||||
// `retry-after: 0`, which made `retryAfterMs ?? DEFAULT` evaluate to 0 — a
|
||||
// ~5s cooldown. The poller then re-hit the endpoint every 60s and re-tripped
|
||||
// the 429 indefinitely. A non-positive Retry-After must use the default.
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-06-20T00:00:00Z'));
|
||||
|
||||
const fetchMock = vi.fn(async () =>
|
||||
makeResponse(429, { 'retry-after': '0' }),
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const { fetchAllClaudeUsage } = await import('./claude-usage.js');
|
||||
|
||||
await fetchAllClaudeUsage();
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
// +70s: past the 60s throttle. With the bug (0 cooldown) this would refetch;
|
||||
// with the default 5-min cooldown it must still be held.
|
||||
vi.setSystemTime(Date.now() + 70_000);
|
||||
const held = await fetchAllClaudeUsage();
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(held[0].usageRateLimited).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps backing off after the 60s throttle elapses but before the cooldown closes', async () => {
|
||||
// This is the core regression: a 93s Retry-After must outlast the 60s
|
||||
// MIN_FETCH_INTERVAL throttle. Without the cooldown the poller would refetch
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
getClaudeCredentialsPath,
|
||||
hasExplicitClaudeCredentialsPath,
|
||||
} from './claude-credentials-path.js';
|
||||
import { DATA_DIR } from './config.js';
|
||||
import { logger } from './logger.js';
|
||||
import {
|
||||
@@ -32,6 +35,14 @@ export interface ClaudeUsageData {
|
||||
const USAGE_ENDPOINT = 'https://api.anthropic.com/api/oauth/usage';
|
||||
const FETCH_TIMEOUT_MS = 10_000;
|
||||
|
||||
// Master gate for credentials-backed usage queries. When false, the module
|
||||
// uses only env-supplied tokens and skips all credentials-file reads, per-
|
||||
// account cache keys, and on-401 refresh attempts. Enabled when either an
|
||||
// explicit CLAUDE_CREDENTIALS_PATH is set or the home fallback is allowed.
|
||||
const USE_CLAUDE_CREDENTIALS_FOR_USAGE =
|
||||
hasExplicitClaudeCredentialsPath() ||
|
||||
process.env.CLAUDE_USAGE_USE_HOME_CREDENTIALS !== 'false';
|
||||
|
||||
interface UsageApiResponse {
|
||||
five_hour?: { utilization: number; resets_at?: string };
|
||||
seven_day?: { utilization: number; resets_at?: string };
|
||||
@@ -112,7 +123,7 @@ export function getUsageCacheWriteKey(
|
||||
token: string,
|
||||
accountIndex?: number,
|
||||
): string {
|
||||
return accountIndex != null
|
||||
return USE_CLAUDE_CREDENTIALS_FOR_USAGE && accountIndex != null
|
||||
? accountCacheKey(accountIndex)
|
||||
: legacyTokenCacheKey(token);
|
||||
}
|
||||
@@ -123,9 +134,11 @@ export function getUsageCacheReadKeys(
|
||||
credentialsAccessToken?: string | null,
|
||||
): string[] {
|
||||
const keys: string[] = [];
|
||||
if (accountIndex != null) keys.push(accountCacheKey(accountIndex));
|
||||
if (USE_CLAUDE_CREDENTIALS_FOR_USAGE && accountIndex != null) {
|
||||
keys.push(accountCacheKey(accountIndex));
|
||||
}
|
||||
|
||||
if (credentialsAccessToken) {
|
||||
if (USE_CLAUDE_CREDENTIALS_FOR_USAGE && credentialsAccessToken) {
|
||||
const credsKey = legacyTokenCacheKey(credentialsAccessToken);
|
||||
if (!keys.includes(credsKey)) keys.push(credsKey);
|
||||
}
|
||||
@@ -265,7 +278,11 @@ async function fetchUsageForToken(
|
||||
// 401 = token expired; 403 = token lacks `user:profile` scope.
|
||||
// Both are recoverable by exchanging refresh_token for a fresh access
|
||||
// token (mint includes current default scope set). Retry once.
|
||||
if (accountIndex != null && !refreshAttempted) {
|
||||
if (
|
||||
USE_CLAUDE_CREDENTIALS_FOR_USAGE &&
|
||||
accountIndex != null &&
|
||||
!refreshAttempted
|
||||
) {
|
||||
try {
|
||||
const { forceRefreshToken } = await import('./token-refresh.js');
|
||||
const newToken = await forceRefreshToken(accountIndex);
|
||||
@@ -303,7 +320,14 @@ async function fetchUsageForToken(
|
||||
if (res.status === 429) {
|
||||
const staleMs = cached ? Date.now() - cached.fetchedAt : 0;
|
||||
const retryAfterMs = parseRetryAfterMs(res.headers.get('retry-after'));
|
||||
const cooldownMs = retryAfterMs ?? DEFAULT_RATE_LIMIT_COOLDOWN_MS;
|
||||
// A `Retry-After` of 0 (or a past HTTP-date that parses to 0) must NOT
|
||||
// disable the backoff — honoring it literally makes the poller retry on
|
||||
// the next 60s tick and re-trip the 429 indefinitely. Treat any
|
||||
// non-positive value as "no usable hint" and fall back to the default.
|
||||
const cooldownMs =
|
||||
retryAfterMs != null && retryAfterMs > 0
|
||||
? retryAfterMs
|
||||
: DEFAULT_RATE_LIMIT_COOLDOWN_MS;
|
||||
const cooldownUntil = Date.now() + cooldownMs + RATE_LIMIT_MARGIN_MS;
|
||||
logger.warn(
|
||||
{
|
||||
@@ -403,12 +427,11 @@ async function fetchUsageForToken(
|
||||
* Uses the current active token from rotation.
|
||||
*/
|
||||
export async function fetchClaudeUsage(): Promise<ClaudeUsageData | null> {
|
||||
// Prefer the access token in ~/.claude/.credentials.json when present.
|
||||
// The static .env token (CLAUDE_CODE_OAUTH_TOKEN) was issued before the
|
||||
// `user:profile` scope was required for /api/oauth/usage and so always 403s.
|
||||
// The credentials file is the canonical source written by `claude auth
|
||||
// login` and kept fresh by token-refresh.ts — its accessToken carries the
|
||||
// full scope set including user:profile.
|
||||
// Prefer the access token in credentials.json when present. The static .env
|
||||
// token (CLAUDE_CODE_OAUTH_TOKEN) was issued before the `user:profile` scope
|
||||
// was required for /api/oauth/usage and so always 403s. The credentials file
|
||||
// is the canonical source written by `claude auth login` and kept fresh by
|
||||
// token-refresh.ts — its accessToken carries the full scope set.
|
||||
const credsToken = readCredentialsAccessToken(0);
|
||||
const token =
|
||||
credsToken || getCurrentToken() || getConfiguredClaudeTokens()[0];
|
||||
@@ -416,7 +439,8 @@ export async function fetchClaudeUsage(): Promise<ClaudeUsageData | null> {
|
||||
logger.debug('No Claude OAuth token available for usage check');
|
||||
return null;
|
||||
}
|
||||
return (await fetchUsageForToken(token, undefined)).usage;
|
||||
// Pass accountIndex=0 so 401/403 → forceRefreshToken retry path engages.
|
||||
return (await fetchUsageForToken(token, 0)).usage;
|
||||
}
|
||||
|
||||
export interface ClaudeAccountProfile {
|
||||
@@ -428,21 +452,18 @@ const profileCache = new Map<number, ClaudeAccountProfile>();
|
||||
|
||||
/**
|
||||
* Read planType from credentials file as fallback when profile API fails.
|
||||
* Account 0: ~/.claude/.credentials.json
|
||||
* Account 1+: ~/.claude-accounts/{index}/.credentials.json
|
||||
* Path is resolved by getClaudeCredentialsPath (honours CLAUDE_CREDENTIALS_PATH
|
||||
* / CLAUDE_ACCOUNTS_DIR / home fallback). Returns null when the credentials
|
||||
* gate is off or no file is found.
|
||||
*/
|
||||
function readCredentialsPlanType(accountIndex: number): string | null {
|
||||
if (!USE_CLAUDE_CREDENTIALS_FOR_USAGE) return null;
|
||||
try {
|
||||
const credsPath =
|
||||
accountIndex === 0
|
||||
? path.join(os.homedir(), '.claude', '.credentials.json')
|
||||
: path.join(
|
||||
os.homedir(),
|
||||
'.claude-accounts',
|
||||
String(accountIndex),
|
||||
'.credentials.json',
|
||||
);
|
||||
if (!fs.existsSync(credsPath)) return null;
|
||||
const credsPath = getClaudeCredentialsPath(accountIndex, {
|
||||
allowHomeFallback:
|
||||
process.env.CLAUDE_USAGE_USE_HOME_CREDENTIALS !== 'false',
|
||||
});
|
||||
if (!credsPath || !fs.existsSync(credsPath)) return null;
|
||||
const data = readJsonFile<{
|
||||
claudeAiOauth?: { subscriptionType?: string };
|
||||
}>(credsPath);
|
||||
@@ -453,17 +474,13 @@ function readCredentialsPlanType(accountIndex: number): string | null {
|
||||
}
|
||||
|
||||
function readCredentialsAccessToken(accountIndex: number): string | null {
|
||||
if (!USE_CLAUDE_CREDENTIALS_FOR_USAGE) return null;
|
||||
try {
|
||||
const credsPath =
|
||||
accountIndex === 0
|
||||
? path.join(os.homedir(), '.claude', '.credentials.json')
|
||||
: path.join(
|
||||
os.homedir(),
|
||||
'.claude-accounts',
|
||||
String(accountIndex),
|
||||
'.credentials.json',
|
||||
);
|
||||
if (!fs.existsSync(credsPath)) return null;
|
||||
const credsPath = getClaudeCredentialsPath(accountIndex, {
|
||||
allowHomeFallback:
|
||||
process.env.CLAUDE_USAGE_USE_HOME_CREDENTIALS !== 'false',
|
||||
});
|
||||
if (!credsPath || !fs.existsSync(credsPath)) return null;
|
||||
const data = readJsonFile<{
|
||||
claudeAiOauth?: { accessToken?: string };
|
||||
}>(credsPath);
|
||||
@@ -519,7 +536,10 @@ async function fetchProfileForToken(
|
||||
export async function fetchAllClaudeProfiles(): Promise<void> {
|
||||
const allTokens = getAllTokens();
|
||||
for (const t of allTokens) {
|
||||
let profile = await fetchProfileForToken(t.token);
|
||||
// Prefer the per-account credentials access token; the static env token
|
||||
// may lack the `user:profile` scope.
|
||||
const credsToken = readCredentialsAccessToken(t.index);
|
||||
let profile = await fetchProfileForToken(credsToken || t.token);
|
||||
|
||||
// Fallback: if profile API failed or returned unknown plan, use credentials file
|
||||
if (!profile || profile.planType === '?') {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { DashboardOptions } from './dashboard-status-content.js';
|
||||
import { formatRoomName } from './unified-dashboard.js';
|
||||
|
||||
function makeOptions(sessionId?: string): DashboardOptions {
|
||||
const sessions: Record<string, string> = sessionId
|
||||
@@ -85,3 +86,32 @@ describe('buildStatusContent', () => {
|
||||
expect(buildStatusContent(makeOptions())).not.toContain('**clone-test**');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatRoomName', () => {
|
||||
it('uses the stored chat name without adding an extra Discord hash', () => {
|
||||
expect(
|
||||
formatRoomName(
|
||||
'dc:123',
|
||||
undefined,
|
||||
'registered-room-name',
|
||||
'My Server #bot-chat',
|
||||
),
|
||||
).toBe('My Server #bot-chat');
|
||||
});
|
||||
|
||||
it('adds a Discord hash for raw channel metadata names', () => {
|
||||
expect(
|
||||
formatRoomName(
|
||||
'dc:123',
|
||||
{
|
||||
name: 'bot-chat',
|
||||
position: 1,
|
||||
category: 'Bots',
|
||||
categoryPosition: 1,
|
||||
},
|
||||
'registered-room-name',
|
||||
'My Server #bot-chat',
|
||||
),
|
||||
).toBe('#bot-chat');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -318,4 +318,14 @@ describe('formatOutbound with tool-call leaks', () => {
|
||||
'Key: sk-ant-XXXXXXXXXXXXXXXXXXXXXXXX\nto=functions.exec_command code {"cmd":"ls"}';
|
||||
expect(formatOutbound(input)).toBe('Key: [REDACTED]');
|
||||
});
|
||||
|
||||
it('redacts a leaked Discord bot token', () => {
|
||||
// Built from parts so the literal never appears in source (push protection).
|
||||
const fakeToken = [
|
||||
'MTA5ODc2NTQzMjEwOTg3NjU0',
|
||||
'GaBcDe',
|
||||
'abcdefghijklmnopqrstuvwxyz0123',
|
||||
].join('.');
|
||||
expect(formatOutbound(`token=${fakeToken}`)).toBe('token=[REDACTED]');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,6 +36,10 @@ import {
|
||||
hasHumanMessageAfterWorkItem,
|
||||
} from './message-runtime-preflight-messages.js';
|
||||
import { deliverCanonicalOutboundMessage } from './ipc-outbound-delivery.js';
|
||||
import {
|
||||
isReloginRequired,
|
||||
RELOGIN_REQUIRED_MESSAGE,
|
||||
} from './token-refresh.js';
|
||||
import { findChannel, formatMessages } from './router.js';
|
||||
import { createScopedLogger, logger } from './logger.js';
|
||||
import type { AgentOutput } from './agent-runner.js';
|
||||
@@ -308,6 +312,15 @@ async function processMissedMessages(
|
||||
return gateOutcome;
|
||||
}
|
||||
|
||||
const reloginOutcome = await runReloginRequiredGate(
|
||||
args,
|
||||
runtime,
|
||||
missedMessages,
|
||||
);
|
||||
if (reloginOutcome !== null) {
|
||||
return reloginOutcome;
|
||||
}
|
||||
|
||||
return runQueuedGroupTurn({
|
||||
chatJid: runtime.chatJid,
|
||||
group: runtime.group,
|
||||
@@ -415,6 +428,48 @@ function advancePastBotOnlyCollaboration(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When Claude OAuth auto-refresh has permanently given up (login lost and the
|
||||
* refresh token is dead — see token-refresh.ts), don't spawn a doomed agent.
|
||||
* Instead reply once, in this chat, asking the user to re-login, then advance
|
||||
* the cursor so a still-logged-out bot doesn't re-notify on every poll. The
|
||||
* next fresh incoming request re-triggers the notice. Recovers automatically
|
||||
* once a manual re-login writes a valid token (isReloginRequired() clears).
|
||||
*
|
||||
* Returns true when the notice was sent (turn handled), null to fall through.
|
||||
*/
|
||||
async function runReloginRequiredGate(
|
||||
args: ProcessGroupMessagesDeps,
|
||||
runtime: RuntimeContext,
|
||||
missedMessages: NewMessage[],
|
||||
): Promise<boolean | null> {
|
||||
// Only claude-code turns depend on the Claude OAuth token. Codex-backed
|
||||
// groups authenticate separately and must not be blocked here.
|
||||
const agentType = runtime.group.agentType ?? 'claude-code';
|
||||
if (agentType !== 'claude-code') return null;
|
||||
if (!isReloginRequired()) return null;
|
||||
|
||||
await deliverSessionCommandMessage(
|
||||
args,
|
||||
runtime.chatJid,
|
||||
RELOGIN_REQUIRED_MESSAGE,
|
||||
);
|
||||
|
||||
const lastMessage = missedMessages[missedMessages.length - 1];
|
||||
if (lastMessage?.seq != null) {
|
||||
advanceLastAgentCursor(
|
||||
args.getLastAgentTimestamps(),
|
||||
args.saveState,
|
||||
runtime.chatJid,
|
||||
lastMessage.seq,
|
||||
);
|
||||
}
|
||||
runtime.log.warn(
|
||||
'Claude OAuth login lost (gave up refreshing) — asked user to re-login instead of running the agent',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function runQueuedRunGates(
|
||||
args: ProcessGroupMessagesDeps,
|
||||
runtime: RuntimeContext,
|
||||
|
||||
@@ -11,10 +11,9 @@ vi.mock('./config.js', async () => {
|
||||
});
|
||||
|
||||
vi.mock('./service-routing.js', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('./service-routing.js')>(
|
||||
'./service-routing.js',
|
||||
);
|
||||
const actual = await vi.importActual<typeof import('./service-routing.js')>(
|
||||
'./service-routing.js',
|
||||
);
|
||||
return { ...actual, hasReviewerLease: vi.fn(() => true) };
|
||||
});
|
||||
|
||||
|
||||
@@ -4172,7 +4172,7 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when the second progress arrives
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('CI 상태 확인 중입니다.\n\n0초'),
|
||||
P('CI 상태 확인 중입니다.'),
|
||||
);
|
||||
// Timer tick edits the tracked progress with updated elapsed time
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -4272,11 +4272,11 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives — sendAndTrack throws, falls back to sendMessage
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('진행 중입니다.\n\n0초'),
|
||||
P('진행 중입니다.'),
|
||||
);
|
||||
expect(channel.sendMessage).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('진행 중입니다.\n\n0초'),
|
||||
P('진행 중입니다.'),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4353,11 +4353,11 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives — sendAndTrack returns null, falls back to sendMessage
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('진행 중입니다.\n\n0초'),
|
||||
P('진행 중입니다.'),
|
||||
);
|
||||
expect(channel.sendMessage).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('진행 중입니다.\n\n0초'),
|
||||
P('진행 중입니다.'),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4461,7 +4461,7 @@ describe('createMessageRuntime', () => {
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledTimes(1);
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('첫 번째 진행상황입니다.\n\n0초'),
|
||||
P('첫 번째 진행상황입니다.'),
|
||||
);
|
||||
// Timer tick updates tracked progress via edit
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -4573,7 +4573,7 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('오래 걸리는 작업입니다.\n\n0초'),
|
||||
P('오래 걸리는 작업입니다.'),
|
||||
);
|
||||
// Timer ticks update the tracked progress with longer durations
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -4679,7 +4679,7 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('테스트를 돌리는 중입니다.\n\n0초'),
|
||||
P('테스트를 돌리는 중입니다.'),
|
||||
);
|
||||
// Timer tick updates tracked progress
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -5088,13 +5088,13 @@ describe('createMessageRuntime', () => {
|
||||
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
chatJid,
|
||||
P('첫 번째 진행상황입니다.\n\n0초'),
|
||||
P('첫 번째 진행상황입니다.'),
|
||||
);
|
||||
// After empty final resets state, third progress flushed when fourth arrives (new message)
|
||||
expect(channel.sendAndTrack).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
chatJid,
|
||||
P('두 번째 진행상황입니다.\n\n0초'),
|
||||
P('두 번째 진행상황입니다.'),
|
||||
);
|
||||
// Timer tick edits the first tracked progress
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -5213,7 +5213,7 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('검증 중입니다.\n\n0초'),
|
||||
P('검증 중입니다.'),
|
||||
);
|
||||
// Once the tracked progress exists, later progress updates replace the visible heading directly.
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -5329,7 +5329,7 @@ describe('createMessageRuntime', () => {
|
||||
// The first flushed progress is still tracked
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('진행 중입니다.\n\n0초'),
|
||||
P('진행 중입니다.'),
|
||||
);
|
||||
// Edit is attempted on the tracked message (first fails, subsequent succeed)
|
||||
expect(channel.editMessage).toHaveBeenCalledWith(
|
||||
@@ -5568,7 +5568,7 @@ describe('createMessageRuntime', () => {
|
||||
// First progress flushed when second arrives
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
P('중간 진행상황입니다.\n\n0초'),
|
||||
P('중간 진행상황입니다.'),
|
||||
);
|
||||
// Error replaces the tracked progress message with the failure final
|
||||
expect(channel.sendMessage).not.toHaveBeenCalled();
|
||||
|
||||
@@ -496,7 +496,10 @@ export class MessageTurnController {
|
||||
? 0
|
||||
: Math.floor((Date.now() - this.progressStartedAt) / 5_000) * 5000;
|
||||
|
||||
const suffix = `\n\n${formatElapsedKorean(elapsedMs)}`;
|
||||
// Skip the elapsed-time label until at least one 5s bucket has elapsed, so
|
||||
// a freshly-created progress message doesn't show a meaningless "0초".
|
||||
const suffix =
|
||||
elapsedMs >= 5000 ? `\n\n${formatElapsedKorean(elapsedMs)}` : '';
|
||||
const body = this.composeProgressBody(text);
|
||||
|
||||
this.persistProgressBody(body);
|
||||
|
||||
48
src/moa.test.ts
Normal file
48
src/moa.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { assertSafeMoaBaseUrl } from './moa.js';
|
||||
|
||||
describe('assertSafeMoaBaseUrl', () => {
|
||||
it('accepts public https endpoints', () => {
|
||||
expect(() =>
|
||||
assertSafeMoaBaseUrl('https://api.moonshot.cn/v1'),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
assertSafeMoaBaseUrl('https://open.bigmodel.cn/api/paas/v4'),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects non-http(s) schemes', () => {
|
||||
expect(() => assertSafeMoaBaseUrl('file:///etc/passwd')).toThrow(
|
||||
/http\(s\)/,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects loopback and localhost', () => {
|
||||
expect(() => assertSafeMoaBaseUrl('http://127.0.0.1:8080')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
expect(() => assertSafeMoaBaseUrl('http://localhost/v1')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects cloud metadata and private ranges', () => {
|
||||
expect(() => assertSafeMoaBaseUrl('http://169.254.169.254/latest')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
expect(() => assertSafeMoaBaseUrl('http://10.0.0.5/v1')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
expect(() => assertSafeMoaBaseUrl('http://192.168.1.1/v1')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
expect(() => assertSafeMoaBaseUrl('http://100.101.210.95/v1')).toThrow(
|
||||
/not allowed/,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects malformed URLs', () => {
|
||||
expect(() => assertSafeMoaBaseUrl('not-a-url')).toThrow(/Invalid/);
|
||||
});
|
||||
});
|
||||
58
src/moa.ts
58
src/moa.ts
@@ -44,6 +44,63 @@ function normalizeError(err: unknown): string {
|
||||
return err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject MoA base URLs that point at loopback / private / link-local hosts.
|
||||
*
|
||||
* MoA reference models are external SaaS endpoints (Kimi, GLM, ...). Allowing
|
||||
* an internal host lets a compromised or misconfigured settings write turn the
|
||||
* server-side MoA fetch into an SSRF probe (e.g. cloud metadata at
|
||||
* 169.254.169.254). Only http(s) to a non-private host is permitted.
|
||||
*/
|
||||
export function assertSafeMoaBaseUrl(baseUrl: string): URL {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(baseUrl);
|
||||
} catch {
|
||||
throw new Error(`Invalid MoA base URL: ${baseUrl}`);
|
||||
}
|
||||
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
||||
throw new Error(`MoA base URL must use http(s): ${baseUrl}`);
|
||||
}
|
||||
if (isPrivateOrLocalHost(parsed.hostname)) {
|
||||
throw new Error(
|
||||
`MoA base URL host is not allowed (loopback/private/link-local): ${parsed.hostname}`,
|
||||
);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function isPrivateOrLocalHost(hostname: string): boolean {
|
||||
const host = hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
||||
if (
|
||||
host === 'localhost' ||
|
||||
host.endsWith('.localhost') ||
|
||||
host.endsWith('.internal') ||
|
||||
host.endsWith('.local')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// IPv6 loopback / link-local / unique-local
|
||||
if (host === '::1' || host === '::') return true;
|
||||
if (
|
||||
host.startsWith('fe80:') ||
|
||||
host.startsWith('fc') ||
|
||||
host.startsWith('fd')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const ipv4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
||||
if (ipv4) {
|
||||
const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
|
||||
if (a === 127 || a === 0 || a === 10) return true; // loopback / this-host / private
|
||||
if (a === 169 && b === 254) return true; // link-local incl. cloud metadata
|
||||
if (a === 172 && b >= 16 && b <= 31) return true; // private
|
||||
if (a === 192 && b === 168) return true; // private
|
||||
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT / tailnet
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function recordReferenceStatus(
|
||||
status: Omit<MoaReferenceStatus, 'checkedAt'>,
|
||||
): MoaReferenceStatus {
|
||||
@@ -69,6 +126,7 @@ async function queryModel(
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
assertSafeMoaBaseUrl(model.baseUrl);
|
||||
const base = model.baseUrl.replace(/\/+$/, '');
|
||||
const isAnthropic = model.apiFormat === 'anthropic';
|
||||
|
||||
|
||||
@@ -161,4 +161,89 @@ describe('paired execution carry-forward attachments', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('carries context forward on a cold start after the previous task already closed', () => {
|
||||
vi.clearAllMocks();
|
||||
const previousTask = buildTask({
|
||||
id: 'task-previous',
|
||||
status: 'completed',
|
||||
completion_reason: 'done',
|
||||
});
|
||||
// No open task exists -> cold start path.
|
||||
vi.mocked(db.getLatestOpenPairedTaskForChat).mockReturnValue(undefined);
|
||||
vi.mocked(db.getLatestPairedTaskForChat).mockReturnValue(previousTask);
|
||||
vi.mocked(db.getPairedTurnOutputs).mockReturnValue([
|
||||
{
|
||||
id: 1,
|
||||
task_id: previousTask.id,
|
||||
turn_number: 1,
|
||||
role: 'owner',
|
||||
output_text:
|
||||
'TASK_DONE\n사용자 액션 아이템: 1. 재배포 2. 서버 업데이트',
|
||||
created_at: '2026-03-28T00:01:00.000Z',
|
||||
},
|
||||
]);
|
||||
|
||||
const resolved = resolveOwnerTaskForHumanMessage({
|
||||
group,
|
||||
chatJid: 'dc:test',
|
||||
roomRoleContext: ownerContext,
|
||||
// no existingTask -> resolves via getLatestOpenPairedTaskForChat (none)
|
||||
});
|
||||
|
||||
expect(resolved.supersededTask).toBeNull();
|
||||
expect(db.insertPairedTurnOutput).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
0,
|
||||
'owner',
|
||||
expect.stringContaining(
|
||||
'TASK_DONE\n사용자 액션 아이템: 1. 재배포 2. 서버 업데이트',
|
||||
),
|
||||
expect.objectContaining({ createdAt: '2026-03-28T00:01:00.000Z' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('carries the last final owner output, skipping an intermediate STEP_DONE', () => {
|
||||
vi.clearAllMocks();
|
||||
const previousTask = buildTask({
|
||||
id: 'task-arbiter',
|
||||
status: 'completed',
|
||||
completion_reason: 'arbiter_escalated',
|
||||
});
|
||||
vi.mocked(db.getLatestOpenPairedTaskForChat).mockReturnValue(undefined);
|
||||
vi.mocked(db.getLatestPairedTaskForChat).mockReturnValue(previousTask);
|
||||
vi.mocked(db.getPairedTurnOutputs).mockReturnValue([
|
||||
{
|
||||
id: 1,
|
||||
task_id: previousTask.id,
|
||||
turn_number: 1,
|
||||
role: 'owner',
|
||||
output_text:
|
||||
'TASK_DONE\n사용자 액션 아이템: 1. 재배포 2. 서버 업데이트',
|
||||
created_at: '2026-03-28T00:01:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
task_id: previousTask.id,
|
||||
turn_number: 3,
|
||||
role: 'owner',
|
||||
output_text: 'STEP_DONE\nexception 경로 버그를 고쳤습니다.',
|
||||
created_at: '2026-03-28T00:03:00.000Z',
|
||||
},
|
||||
]);
|
||||
|
||||
resolveOwnerTaskForHumanMessage({
|
||||
group,
|
||||
chatJid: 'dc:test',
|
||||
roomRoleContext: ownerContext,
|
||||
});
|
||||
|
||||
expect(db.insertPairedTurnOutput).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
0,
|
||||
'owner',
|
||||
expect.stringContaining('사용자 액션 아이템'),
|
||||
expect.objectContaining({ createdAt: '2026-03-28T00:01:00.000Z' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -677,7 +677,9 @@ describe('paired execution context', () => {
|
||||
group,
|
||||
chatJid: 'dc:test',
|
||||
runId: 'run-host-reviewer',
|
||||
roomRoleContext: reviewerContext,
|
||||
// Force a claude-code reviewer so this test exercises the Claude
|
||||
// read-only branch regardless of the deployment's default reviewer type.
|
||||
roomRoleContext: { ...reviewerContext, reviewerAgentType: 'claude-code' },
|
||||
});
|
||||
|
||||
expect(result?.envOverrides).toMatchObject({
|
||||
|
||||
@@ -229,14 +229,22 @@ function cancelOutstandingFinalizeOwnerTurn(task: PairedTask): void {
|
||||
);
|
||||
}
|
||||
|
||||
function getLatestTurnOutputByRole(
|
||||
taskId: string,
|
||||
role: PairedRoomRole,
|
||||
): PairedTurnOutput | null {
|
||||
function isIntermediateStepOutput(outputText: string): boolean {
|
||||
// STEP_DONE marks an intermediate step that keeps the task active; it is not
|
||||
// the user-facing finalize summary, so it is a poor carry-forward anchor.
|
||||
return /^\s*STEP_DONE\b/.test(outputText);
|
||||
}
|
||||
|
||||
function getLatestOwnerFinalOutput(taskId: string): PairedTurnOutput | null {
|
||||
const ownerOutputs = [...getPairedTurnOutputs(taskId)]
|
||||
.reverse()
|
||||
.filter((output) => output.role === 'owner');
|
||||
return (
|
||||
[...getPairedTurnOutputs(taskId)]
|
||||
.reverse()
|
||||
.find((output) => output.role === role) ?? null
|
||||
ownerOutputs.find(
|
||||
(output) => !isIntermediateStepOutput(output.output_text),
|
||||
) ??
|
||||
ownerOutputs[0] ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,10 +256,7 @@ function carryForwardLatestOwnerFinal(args: {
|
||||
return;
|
||||
}
|
||||
|
||||
const latestOwnerFinal = getLatestTurnOutputByRole(
|
||||
args.sourceTask.id,
|
||||
'owner',
|
||||
);
|
||||
const latestOwnerFinal = getLatestOwnerFinalOutput(args.sourceTask.id);
|
||||
if (!latestOwnerFinal) {
|
||||
return;
|
||||
}
|
||||
@@ -293,16 +298,27 @@ export function resolveOwnerTaskForHumanMessage(args: {
|
||||
args.existingTask ?? getLatestOpenPairedTaskForChat(args.chatJid) ?? null;
|
||||
|
||||
if (!existing) {
|
||||
maybeRecordTaskDoneReopen(getLatestPairedTaskForChat(args.chatJid) ?? null);
|
||||
const previousTask = getLatestPairedTaskForChat(args.chatJid) ?? null;
|
||||
maybeRecordTaskDoneReopen(previousTask);
|
||||
const newTask = canonicalWorkDir
|
||||
? createActiveTaskForRoom({
|
||||
group: args.group,
|
||||
chatJid: args.chatJid,
|
||||
canonicalWorkDir,
|
||||
roomRoleContext: args.roomRoleContext,
|
||||
})
|
||||
: null;
|
||||
// Cold start after the previous task already closed (completed): seed the
|
||||
// fresh task with the previous task's latest owner final so the owner keeps
|
||||
// continuity across sessions instead of answering with "no context".
|
||||
if (newTask && previousTask) {
|
||||
carryForwardLatestOwnerFinal({
|
||||
sourceTask: previousTask,
|
||||
targetTask: newTask,
|
||||
});
|
||||
}
|
||||
return {
|
||||
task: canonicalWorkDir
|
||||
? createActiveTaskForRoom({
|
||||
group: args.group,
|
||||
chatJid: args.chatJid,
|
||||
canonicalWorkDir,
|
||||
roomRoleContext: args.roomRoleContext,
|
||||
})
|
||||
: null,
|
||||
task: newTask,
|
||||
supersededTask: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ const SECRET_PATTERNS: RegExp[] = [
|
||||
/glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT
|
||||
/AKIA[A-Z0-9]{16}/g, // AWS Access Key
|
||||
/Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT
|
||||
// Discord bot token: base64 id "." 6-char ts "." 27+ char hmac
|
||||
/\b[MN][A-Za-z0-9_-]{23,}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}\b/g,
|
||||
];
|
||||
|
||||
function redactSecrets(text: string): string {
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { getClaudeCredentialsPath } from './claude-credentials-path.js';
|
||||
import {
|
||||
CURRENT_RUNTIME_AGENT_TYPE,
|
||||
DATA_DIR,
|
||||
@@ -236,7 +237,8 @@ export function getRuntimeInventory(
|
||||
pathSnapshot('Claude settings.json', claudeSettingsPath),
|
||||
pathSnapshot(
|
||||
'Claude credentials',
|
||||
path.join(homeDir, '.claude', '.credentials.json'),
|
||||
getClaudeCredentialsPath(0, { allowHomeFallback: true }) ??
|
||||
path.join(homeDir, '.claude', '.credentials.json'),
|
||||
),
|
||||
],
|
||||
skillDirs: [
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('service-routing global failover', () => {
|
||||
expect(getEffectiveChannelLease('dc:paired')).toMatchObject({
|
||||
chat_jid: 'dc:paired',
|
||||
owner_service_id: 'codex-review',
|
||||
reviewer_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_failover_active: true,
|
||||
reason: 'claude-429',
|
||||
explicit: true,
|
||||
@@ -115,8 +115,8 @@ describe('service-routing global failover', () => {
|
||||
expect(getGlobalFailoverInfo().active).toBe(false);
|
||||
expect(getEffectiveChannelLease('dc:paired')).toMatchObject({
|
||||
chat_jid: 'dc:paired',
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: 'claude',
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
});
|
||||
@@ -141,7 +141,7 @@ describe('service-routing global failover', () => {
|
||||
|
||||
expect(getEffectiveChannelLease('dc:explicit-single')).toMatchObject({
|
||||
chat_jid: 'dc:explicit-single',
|
||||
owner_service_id: 'codex-main',
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: null,
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
@@ -210,7 +210,7 @@ describe('service-routing global failover', () => {
|
||||
expect(getEffectiveChannelLease('dc:explicit-tribunal')).toMatchObject({
|
||||
chat_jid: 'dc:explicit-tribunal',
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
});
|
||||
@@ -231,7 +231,7 @@ describe('service-routing global failover', () => {
|
||||
).toMatchObject({
|
||||
chat_jid: 'dc:explicit-tribunal-codex',
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
});
|
||||
@@ -263,7 +263,7 @@ describe('service-routing global failover', () => {
|
||||
it('defaults to the configured owner service for chats without canonical room settings', () => {
|
||||
expect(getEffectiveChannelLease('dc:unregistered')).toMatchObject({
|
||||
chat_jid: 'dc:unregistered',
|
||||
owner_service_id: 'codex-main',
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: null,
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
@@ -289,7 +289,7 @@ describe('service-routing global failover', () => {
|
||||
|
||||
expect(getEffectiveChannelLease('dc:legacy-only')).toMatchObject({
|
||||
chat_jid: 'dc:legacy-only',
|
||||
owner_service_id: 'codex-main',
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: null,
|
||||
owner_failover_active: false,
|
||||
explicit: false,
|
||||
@@ -344,6 +344,8 @@ describe('stored lease ids as SSOT', () => {
|
||||
activated_at: '2026-04-09T00:00:00.000Z',
|
||||
reason: 'ssot-test',
|
||||
});
|
||||
// A stored reviewer lease only applies while the room is paired (tribunal).
|
||||
setExplicitRoomMode('dc:stored-reviewer-ssot', 'tribunal');
|
||||
refreshChannelOwnerCache(true);
|
||||
|
||||
const lease = getEffectiveChannelLease('dc:stored-reviewer-ssot');
|
||||
@@ -359,3 +361,53 @@ describe('stored lease ids as SSOT', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single-mode rooms never carry a reviewer lease', () => {
|
||||
it('suppresses a stale stored reviewer lease when the room is single, keeping the owner', () => {
|
||||
// Reproduces the incident: a room was tribunal, got a stored reviewer
|
||||
// lease, then was switched to single. The stale reviewer must not route
|
||||
// single-mode messages into the paired path.
|
||||
setChannelOwnerLease({
|
||||
chat_jid: 'dc:stale-single',
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_agent_type: 'codex',
|
||||
reviewer_agent_type: 'codex',
|
||||
activated_at: '2026-05-27T00:00:00.000Z',
|
||||
reason: 'was-tribunal',
|
||||
});
|
||||
setExplicitRoomMode('dc:stale-single', 'single');
|
||||
refreshChannelOwnerCache(true);
|
||||
|
||||
expect(getEffectiveChannelLease('dc:stale-single')).toMatchObject({
|
||||
chat_jid: 'dc:stale-single',
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: null,
|
||||
arbiter_service_id: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('restores the stored reviewer lease when the room is switched back to tribunal', () => {
|
||||
setChannelOwnerLease({
|
||||
chat_jid: 'dc:toggle-mode',
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: 'codex-review',
|
||||
owner_agent_type: 'codex',
|
||||
reviewer_agent_type: 'codex',
|
||||
activated_at: '2026-05-27T00:00:00.000Z',
|
||||
reason: 'toggle',
|
||||
});
|
||||
setExplicitRoomMode('dc:toggle-mode', 'single');
|
||||
refreshChannelOwnerCache(true);
|
||||
expect(
|
||||
getEffectiveChannelLease('dc:toggle-mode').reviewer_service_id,
|
||||
).toBeNull();
|
||||
|
||||
setExplicitRoomMode('dc:toggle-mode', 'tribunal');
|
||||
refreshChannelOwnerCache(true);
|
||||
expect(getEffectiveChannelLease('dc:toggle-mode')).toMatchObject({
|
||||
owner_service_id: 'codex-main',
|
||||
reviewer_service_id: 'codex-review',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,13 +146,36 @@ function getDefaultLease(chatJid: string): EffectiveChannelLease {
|
||||
};
|
||||
}
|
||||
|
||||
function enforceRoomModeOnLease(
|
||||
chatJid: string,
|
||||
lease: EffectiveChannelLease,
|
||||
): EffectiveChannelLease {
|
||||
// Invariant: a single-mode room never runs a reviewer/arbiter, even if a
|
||||
// stored lease still carries them from when the room was tribunal. Without
|
||||
// this, a stale reviewer lease keeps routing single-mode messages into the
|
||||
// paired path, where they stall forever on a stuck/mismatched execution
|
||||
// lease (the "task revision was already claimed elsewhere" hang). The stored
|
||||
// row is left untouched, so switching the room back to tribunal restores it.
|
||||
if (
|
||||
(lease.reviewer_service_id == null && lease.arbiter_service_id == null) ||
|
||||
getEffectiveRuntimeRoomMode(chatJid) !== 'single'
|
||||
) {
|
||||
return lease;
|
||||
}
|
||||
return {
|
||||
...lease,
|
||||
reviewer_agent_type: null,
|
||||
arbiter_agent_type: null,
|
||||
reviewer_service_id: null,
|
||||
arbiter_service_id: null,
|
||||
};
|
||||
}
|
||||
|
||||
function getStoredOrDefaultLease(chatJid: string): EffectiveChannelLease {
|
||||
refreshChannelOwnerCache();
|
||||
const row = leaseCache.get(chatJid);
|
||||
if (row) {
|
||||
return normalizeLeaseRow(row, true);
|
||||
}
|
||||
return getDefaultLease(chatJid);
|
||||
const lease = row ? normalizeLeaseRow(row, true) : getDefaultLease(chatJid);
|
||||
return enforceRoomModeOnLease(chatJid, lease);
|
||||
}
|
||||
|
||||
export function refreshChannelOwnerCache(force = false): void {
|
||||
|
||||
@@ -60,6 +60,26 @@ describe('shouldResetSessionOnAgentFailure', () => {
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('resets the session on context-window overflow (prompt too long)', () => {
|
||||
expect(
|
||||
shouldResetSessionOnAgentFailure({
|
||||
result: null,
|
||||
error:
|
||||
'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 215000 tokens > 200000 maximum"}}',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('resets the session when max_tokens exceeds the context limit', () => {
|
||||
expect(
|
||||
shouldResetSessionOnAgentFailure({
|
||||
result:
|
||||
'input length and `max_tokens` exceed context limit: 190000 + 20000 > 200000',
|
||||
error: undefined,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldRetryFreshSessionOnAgentFailure', () => {
|
||||
|
||||
@@ -9,6 +9,12 @@ const SESSION_RESET_PATTERNS = [
|
||||
/400[^\n]*thinking/i,
|
||||
/thinking[^\n]*block[^\n]*(?:invalid|error|signature)/i,
|
||||
/invalid[^\n]*signature[^\n]*thinking/i,
|
||||
// Context-window overflow: the resumed session has grown past the model's
|
||||
// limit, so even auto-compaction fails with "Prompt is too long". Without
|
||||
// this, the same oversized session is reused every turn and the channel
|
||||
// loops forever on the same error. Drop it so the next turn starts fresh.
|
||||
/prompt is too long/i,
|
||||
/input length and .*exceed context limit/i,
|
||||
];
|
||||
|
||||
const SESSION_RETRY_PATTERNS = [
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AgentType } from './types.js';
|
||||
export interface StatusSnapshotEntry {
|
||||
jid: string;
|
||||
name: string;
|
||||
chatName?: string;
|
||||
folder: string;
|
||||
agentType: AgentType;
|
||||
status: GroupStatus['status'];
|
||||
|
||||
@@ -8,11 +8,38 @@ import {
|
||||
formatStatusHeader,
|
||||
msUntilNextMinuteBoundary,
|
||||
getDashboardDuplicateCleanupIntervalMs,
|
||||
readCpuUtilizationPct,
|
||||
renderUsageTable,
|
||||
resetCpuUtilizationSample,
|
||||
shouldPurgeDashboardChannelOnStart,
|
||||
summarizeWatcherTasks,
|
||||
} from './unified-dashboard.js';
|
||||
|
||||
describe('readCpuUtilizationPct', () => {
|
||||
it('computes busy percentage from consecutive /proc/stat samples', () => {
|
||||
resetCpuUtilizationSample();
|
||||
expect(
|
||||
readCpuUtilizationPct(() => 'cpu 100 0 100 800 0 0 0 0 0 0\n'),
|
||||
).toBeNull();
|
||||
// +100 total ticks: +30 busy, +70 idle => 30%.
|
||||
expect(
|
||||
readCpuUtilizationPct(() => 'cpu 115 0 115 870 0 0 0 0 0 0\n'),
|
||||
).toBe(30);
|
||||
});
|
||||
|
||||
it('counts iowait as idle and rejects malformed samples', () => {
|
||||
resetCpuUtilizationSample();
|
||||
expect(readCpuUtilizationPct(() => 'not-a-cpu-line\n')).toBeNull();
|
||||
expect(
|
||||
readCpuUtilizationPct(() => 'cpu 100 0 100 700 100 0 0 0 0 0\n'),
|
||||
).toBeNull();
|
||||
// +100 total, all +100 is iowait: CPU busy must be 0%, not 100%.
|
||||
expect(
|
||||
readCpuUtilizationPct(() => 'cpu 100 0 100 700 200 0 0 0 0 0\n'),
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('summarizeWatcherTasks', () => {
|
||||
it('counts active and paused watcher tasks only', () => {
|
||||
const summary = summarizeWatcherTasks([
|
||||
|
||||
@@ -488,6 +488,47 @@ function buildStatusContent(): string {
|
||||
return `${header}\n\n${sections}`;
|
||||
}
|
||||
|
||||
/** Previous /proc/stat sample for CPU utilization deltas. */
|
||||
let lastCpuSample: { idle: number; total: number } | null = null;
|
||||
|
||||
/**
|
||||
* Real CPU utilization percent from /proc/stat deltas between calls.
|
||||
* First call (no previous sample) and non-Linux hosts return null.
|
||||
* Exported for testing alongside resetCpuUtilizationSample.
|
||||
*/
|
||||
export function readCpuUtilizationPct(
|
||||
readStat: () => string = () => fs.readFileSync('/proc/stat', 'utf-8'),
|
||||
): number | null {
|
||||
try {
|
||||
const cpuLine = readStat()
|
||||
.split('\n')
|
||||
.find((line) => line.startsWith('cpu '));
|
||||
if (!cpuLine) return null;
|
||||
const fields = cpuLine.trim().split(/\s+/).slice(1).map(Number);
|
||||
if (fields.length < 5 || fields.some((n) => !Number.isFinite(n))) {
|
||||
return null;
|
||||
}
|
||||
// user nice system idle iowait irq softirq steal ...
|
||||
const idle = fields[3] + (fields[4] ?? 0); // idle + iowait
|
||||
const total = fields.reduce((a, b) => a + b, 0);
|
||||
const prev = lastCpuSample;
|
||||
lastCpuSample = { idle, total };
|
||||
if (!prev || total <= prev.total) return null;
|
||||
const totalDelta = total - prev.total;
|
||||
const idleDelta = idle - prev.idle;
|
||||
return Math.round(
|
||||
Math.min(100, Math.max(0, ((totalDelta - idleDelta) / totalDelta) * 100)),
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Test-only: reset the CPU sample so the next read starts fresh. */
|
||||
export function resetCpuUtilizationSample(): void {
|
||||
lastCpuSample = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render usage table lines from two row groups (Claude and Codex).
|
||||
* Returns rendered lines including code block markers.
|
||||
@@ -517,6 +558,10 @@ export function renderUsageTable(
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// Missing-window placeholder. Must mirror a real cell's char composition
|
||||
// (5 ambiguous-width bar chars + 4 ASCII): mobile fonts render block/box
|
||||
// chars double-width, so a plain '—' cell breaks column alignment.
|
||||
const emptyCell = '─'.repeat(5) + ' ';
|
||||
const renderRows = (rows: UsageRow[]) => {
|
||||
for (const row of rows) {
|
||||
if (row.error) {
|
||||
@@ -529,11 +574,11 @@ export function renderUsageTable(
|
||||
const h5 =
|
||||
row.h5pct >= 0
|
||||
? `${bar(row.h5pct)}${String(row.h5pct).padStart(3)}%`
|
||||
: ' — ';
|
||||
: emptyCell;
|
||||
const d7 =
|
||||
row.d7pct >= 0
|
||||
? `${bar(row.d7pct)}${String(row.d7pct).padStart(3)}%`
|
||||
: ' — ';
|
||||
: emptyCell;
|
||||
lines.push(`${padName(row.name)}${h5} ${d7}`);
|
||||
const r5 = compactReset(row.h5reset);
|
||||
const r7 = compactReset(row.d7reset);
|
||||
@@ -626,7 +671,11 @@ async function buildUsageContent(): Promise<string> {
|
||||
|
||||
const loadAvg = os.loadavg();
|
||||
const cpuCount = os.cpus().length;
|
||||
const cpuPct = Math.round((loadAvg[1] / cpuCount) * 100);
|
||||
// Real CPU utilization from /proc/stat deltas between renders.
|
||||
// Load-average-based percent counts D-state (I/O-wait) processes, so a
|
||||
// writeback storm once rendered as "CPU 3104%" despite idle CPUs.
|
||||
const cpuPct = readCpuUtilizationPct() ?? 0;
|
||||
const loadPerCore = loadAvg[0] / cpuCount;
|
||||
const totalMem = os.totalmem();
|
||||
// os.freemem() includes buffers/cache as "used" — misleading.
|
||||
// Read MemAvailable from /proc/meminfo for actual available memory.
|
||||
@@ -696,6 +745,12 @@ async function buildUsageContent(): Promise<string> {
|
||||
|
||||
lines.push('```');
|
||||
lines.push(`${'CPU'.padEnd(8)}${bar(cpuPct)} ${String(cpuPct).padStart(3)}%`);
|
||||
// Raw load average with core count: I/O storms (D-state pileups) show up
|
||||
// here without masquerading as CPU usage. Flag when load exceeds cores.
|
||||
const loadFlag = loadPerCore > 1 ? ' ▲' : '';
|
||||
lines.push(
|
||||
`${'Load'.padEnd(8)}${loadAvg[0] >= 100 ? loadAvg[0].toFixed(0) : loadAvg[0].toFixed(1)}/${cpuCount}cpu${loadFlag}`,
|
||||
);
|
||||
lines.push(
|
||||
`${'Memory'.padEnd(8)}${bar(memPct)} ${String(memPct).padStart(3)}% ${memUsedGB}/${memTotalGB}GB`,
|
||||
);
|
||||
|
||||
@@ -151,10 +151,14 @@ function readPackageScripts(repoDir: string): Record<string, string> {
|
||||
function detectDirectRuntimeVersion(
|
||||
command: Pick<VerificationCommandSpec, 'file'>,
|
||||
): string {
|
||||
// The command file may be a bare name ("bun") or a resolved absolute path
|
||||
// ("/home/claude/.bun/bin/bun"); match on the basename so version detection
|
||||
// still fires instead of degrading to "host:<path>".
|
||||
const runtimeName = path.basename(command.file);
|
||||
try {
|
||||
switch (command.file) {
|
||||
switch (runtimeName) {
|
||||
case 'bun': {
|
||||
const bunVersion = execFileSync('bun', ['--version'], {
|
||||
const bunVersion = execFileSync(command.file, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: 5000,
|
||||
|
||||
@@ -5,7 +5,10 @@ import path from 'path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import type { NewMessage, PairedTask, RegisteredGroup } from './types.js';
|
||||
import { createWebDashboardHandler } from './web-dashboard-server.js';
|
||||
import {
|
||||
assertDashboardAuthPosture,
|
||||
createWebDashboardHandler,
|
||||
} from './web-dashboard-server.js';
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
@@ -45,6 +48,36 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('assertDashboardAuthPosture', () => {
|
||||
it('allows loopback binding without a token', () => {
|
||||
expect(() => assertDashboardAuthPosture('127.0.0.1', '')).not.toThrow();
|
||||
expect(() => assertDashboardAuthPosture('localhost', '')).not.toThrow();
|
||||
});
|
||||
|
||||
it('allows any host when a token is set', () => {
|
||||
expect(() => assertDashboardAuthPosture('0.0.0.0', 'secret')).not.toThrow();
|
||||
expect(() =>
|
||||
assertDashboardAuthPosture('203.0.113.5', 'secret'),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('warns but starts on a private / tailnet host without a token', () => {
|
||||
expect(() =>
|
||||
assertDashboardAuthPosture('100.101.210.95', ''),
|
||||
).not.toThrow();
|
||||
expect(() => assertDashboardAuthPosture('192.168.1.10', '')).not.toThrow();
|
||||
});
|
||||
|
||||
it('refuses a public / all-interface bind without a token', () => {
|
||||
expect(() => assertDashboardAuthPosture('0.0.0.0', '')).toThrow(
|
||||
/WEB_DASHBOARD_TOKEN/,
|
||||
);
|
||||
expect(() => assertDashboardAuthPosture('203.0.113.5', '')).toThrow(
|
||||
/WEB_DASHBOARD_TOKEN/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('web dashboard server handler', () => {
|
||||
it('serves health and overview JSON without requiring Discord', async () => {
|
||||
const handler = createWebDashboardHandler({
|
||||
|
||||
@@ -504,6 +504,54 @@ export function createWebDashboardHandler(
|
||||
};
|
||||
}
|
||||
|
||||
function isLoopbackHost(host: string): boolean {
|
||||
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
||||
return h === 'localhost' || h === '::1' || /^127\./.test(h);
|
||||
}
|
||||
|
||||
function isPrivateBindHost(host: string): boolean {
|
||||
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
||||
if (h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) {
|
||||
return true;
|
||||
}
|
||||
const ipv4 = h.match(/^(\d{1,3})\.(\d{1,3})\./);
|
||||
if (!ipv4) return false;
|
||||
const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
|
||||
if (a === 10) return true;
|
||||
if (a === 169 && b === 254) return true;
|
||||
if (a === 172 && b >= 16 && b <= 31) return true;
|
||||
if (a === 192 && b === 168) return true;
|
||||
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT / tailnet
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse to expose an unauthenticated dashboard on a public interface.
|
||||
*
|
||||
* With no token the API auth gate passes for everyone (see
|
||||
* isDashboardApiAuthorized). Binding that to a public interface / all
|
||||
* interfaces (0.0.0.0) would let any reachable client drive agents and restart
|
||||
* the service, so we fail fast. Loopback is fine; private / tailnet binds warn
|
||||
* loudly but still start so existing deployments keep running.
|
||||
*/
|
||||
export function assertDashboardAuthPosture(host: string, token: string): void {
|
||||
if (token) return;
|
||||
if (isLoopbackHost(host)) return;
|
||||
if (isPrivateBindHost(host)) {
|
||||
logger.warn(
|
||||
{ host },
|
||||
'Web dashboard is bound to a non-loopback host without WEB_DASHBOARD_TOKEN; ' +
|
||||
'anyone who can reach this host can drive agents and restart the service. ' +
|
||||
'Set WEB_DASHBOARD_TOKEN to require authentication.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`Refusing to start web dashboard on public host "${host}" without WEB_DASHBOARD_TOKEN. ` +
|
||||
'Set WEB_DASHBOARD_TOKEN or bind to 127.0.0.1.',
|
||||
);
|
||||
}
|
||||
|
||||
export function startWebDashboardServer(
|
||||
opts: {
|
||||
enabled?: boolean;
|
||||
@@ -521,6 +569,7 @@ export function startWebDashboardServer(
|
||||
const host = opts.host ?? WEB_DASHBOARD.host;
|
||||
const port = opts.port ?? WEB_DASHBOARD.port;
|
||||
const staticDir = opts.staticDir ?? WEB_DASHBOARD.staticDir;
|
||||
assertDashboardAuthPosture(host, WEB_DASHBOARD.token);
|
||||
const server = Bun.serve({
|
||||
hostname: host,
|
||||
port,
|
||||
|
||||
Reference in New Issue
Block a user