node dependency updates (#2259)

chore: node dependency updates (#2259 — thanks @backryun)
This commit is contained in:
backryun
2026-05-15 08:20:54 +09:00
committed by GitHub
parent aa0e312d8a
commit c6b269a4d5
32 changed files with 1556 additions and 766 deletions

View File

@@ -92,7 +92,7 @@ OMNIROUTE_USE_TURBOPACK=1
# OMNIROUTE_PORT=20128 # OMNIROUTE_PORT=20128
# Hostname/bind address for the Next.js server. # Hostname/bind address for the Next.js server.
# Used by: scripts/run-next.mjs (HOST), Playwright runner (HOSTNAME). # Used by: scripts/dev/run-next.mjs (HOST), Playwright runner (HOSTNAME).
# Default: 0.0.0.0 (HOST) / 127.0.0.1 (HOSTNAME inside tests). # Default: 0.0.0.0 (HOST) / 127.0.0.1 (HOSTNAME inside tests).
#HOST=0.0.0.0 #HOST=0.0.0.0
#HOSTNAME=127.0.0.1 #HOSTNAME=127.0.0.1
@@ -955,7 +955,7 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/utils/cursorVersionDetector.ts. Default: probed automatically. # Used by: open-sse/utils/cursorVersionDetector.ts. Default: probed automatically.
# CURSOR_STATE_DB_PATH= # CURSOR_STATE_DB_PATH=
# Direct Cursor bearer token used by scripts/cursor-tap.cjs (developer tooling). # Direct Cursor bearer token used by scripts/ad-hoc/cursor-tap.cjs (developer tooling).
# CURSOR_TOKEN= # CURSOR_TOKEN=
# Log Responses API SSE-to-JSON translation details. # Log Responses API SSE-to-JSON translation details.
@@ -1073,8 +1073,8 @@ APP_LOG_TO_FILE=true
# ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════
# 25. TEST & E2E # 25. TEST & E2E
# ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════
# Used by scripts/run-next-playwright.mjs, scripts/smoke-electron-packaged.mjs, # Used by scripts/dev/run-next-playwright.mjs, scripts/dev/smoke-electron-packaged.mjs,
# scripts/run-ecosystem-tests.mjs and scripts/uninstall.mjs. # scripts/dev/run-ecosystem-tests.mjs and scripts/build/uninstall.mjs.
# Production deployments should leave every value below unset. # Production deployments should leave every value below unset.
# E2E bootstrap mode for the Playwright runner. Accepted: auth | fresh | reuse. # E2E bootstrap mode for the Playwright runner. Accepted: auth | fresh | reuse.
@@ -1116,7 +1116,7 @@ APP_LOG_TO_FILE=true
# Number of parallel translation requests (default 4). # Number of parallel translation requests (default 4).
# OMNIROUTE_TRANSLATION_CONCURRENCY=4 # OMNIROUTE_TRANSLATION_CONCURRENCY=4
# Electron smoke harness (used by scripts/smoke-electron-packaged.mjs). # Electron smoke harness (used by scripts/dev/smoke-electron-packaged.mjs).
# ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login # ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login
# ELECTRON_SMOKE_TIMEOUT_MS=45000 # ELECTRON_SMOKE_TIMEOUT_MS=45000
# ELECTRON_SMOKE_SETTLE_MS=2000 # ELECTRON_SMOKE_SETTLE_MS=2000

View File

@@ -165,7 +165,7 @@ body:
description: "Which commands or tests should prove this bug is fixed?" description: "Which commands or tests should prove this bug is fixed?"
placeholder: | placeholder: |
Example: Example:
- node --import tsx/esm --test tests/unit/my-file.test.ts - node --import tsx --test tests/unit/my-file.test.ts
- npm run test:coverage - npm run test:coverage
validations: validations:
required: false required: false

View File

@@ -59,7 +59,7 @@ body:
description: "List the commands that must pass before this issue can be closed." description: "List the commands that must pass before this issue can be closed."
placeholder: | placeholder: |
Example: Example:
- node --import tsx/esm --test tests/unit/my-suite.test.ts - node --import tsx --test tests/unit/my-suite.test.ts
- npm run test:coverage - npm run test:coverage
validations: validations:
required: true required: true

View File

@@ -207,7 +207,7 @@ jobs:
cache: npm cache: npm
- run: npm ci - run: npm ci
- run: npm run check:node-runtime - run: npm run check:node-runtime
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts - run: node --import tsx --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
node-24-compat: node-24-compat:
name: Node 24 Compatibility (${{ matrix.shard }}/2) name: Node 24 Compatibility (${{ matrix.shard }}/2)
@@ -231,7 +231,7 @@ jobs:
- run: npm ci - run: npm ci
- run: npm run check:node-runtime - run: npm run check:node-runtime
- run: npm run build - run: npm run build
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts - run: node --import tsx --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
node-26-compat: node-26-compat:
name: Node 26 Compatibility (${{ matrix.shard }}/2) name: Node 26 Compatibility (${{ matrix.shard }}/2)
@@ -255,7 +255,7 @@ jobs:
- run: npm ci - run: npm ci
- run: npm run check:node-runtime - run: npm run check:node-runtime
- run: npm run build - run: npm run build
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts - run: node --import tsx --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
test-coverage: test-coverage:
name: Coverage name: Coverage
@@ -462,7 +462,7 @@ jobs:
cache: npm cache: npm
- run: npm ci - run: npm ci
- run: npm run check:node-runtime - run: npm run check:node-runtime
- run: node --import tsx/esm --test --test-force-exit --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts - run: node --import tsx --test --test-force-exit --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts
test-security: test-security:
name: Security Tests name: Security Tests

View File

@@ -360,7 +360,7 @@ async function checkNativeBinary(rootDir) {
try { try {
const { isNativeBinaryCompatible } = await import( const { isNativeBinaryCompatible } = await import(
pathToFileURL(path.join(rootDir, "scripts", "native-binary-compat.mjs")).href pathToFileURL(path.join(rootDir, "scripts", "build", "native-binary-compat.mjs")).href
); );
const compatible = isNativeBinaryCompatible(binaryPath); const compatible = isNativeBinaryCompatible(binaryPath);
if (!compatible) { if (!compatible) {

View File

@@ -37,7 +37,7 @@ export async function startMcpCli(rootDir = ROOT) {
} }
// `tsx` loader is only required for local `.ts` fallback; JS entry works without it. // `tsx` loader is only required for local `.ts` fallback; JS entry works without it.
const loaderArgs = mcpEntry.endsWith(".ts") ? ["--import", "tsx/esm"] : []; const loaderArgs = mcpEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [...loaderArgs, mcpEntry], { const child = spawn(process.execPath, [...loaderArgs, mcpEntry], {

View File

@@ -22,7 +22,7 @@ import { existsSync, readFileSync } from "node:fs";
import { join, dirname } from "node:path"; import { join, dirname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url"; import { fileURLToPath, pathToFileURL } from "node:url";
import { homedir, platform } from "node:os"; import { homedir, platform } from "node:os";
import { isNativeBinaryCompatible } from "../scripts/native-binary-compat.mjs"; import { isNativeBinaryCompatible } from "../scripts/build/native-binary-compat.mjs";
import { getNodeRuntimeSupport, getNodeRuntimeWarning } from "./nodeRuntimeSupport.mjs"; import { getNodeRuntimeSupport, getNodeRuntimeWarning } from "./nodeRuntimeSupport.mjs";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);

View File

@@ -105,19 +105,19 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
## 3. Network & Ports ## 3. Network & Ports
| Variable | Default | Source File | Description | | Variable | Default | Source File | Description |
| ------------------------- | ------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | ------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). | | `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. | | `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. | | `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. | | `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. | | `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. | | `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. | | `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. | | `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
| `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. | | `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. |
| `HOST` | `0.0.0.0` | `scripts/run-next.mjs` | Bind address for the Next.js dev/start server. Overrides the default `0.0.0.0` when set. | | `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Bind address for the Next.js dev/start server. Overrides the default `0.0.0.0` when set. |
| `HOSTNAME` | `127.0.0.1` | `scripts/run-next-playwright.mjs` | Bind address used by the Playwright runner when launching Next.js. Defaults to `127.0.0.1` for hermetic tests. | | `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Bind address used by the Playwright runner when launching Next.js. Defaults to `127.0.0.1` for hermetic tests. |
### Port Modes ### Port Modes
@@ -666,7 +666,7 @@ Anthropic-compatible provider instead.
| `CURSOR_DUMP_FILE` | _(unset)_ | `open-sse/executors/cursor.ts` | Optional file path that receives raw decoded Cursor chunks when `CURSOR_DEBUG=1`. | | `CURSOR_DUMP_FILE` | _(unset)_ | `open-sse/executors/cursor.ts` | Optional file path that receives raw decoded Cursor chunks when `CURSOR_DEBUG=1`. |
| `CURSOR_STREAM_TIMEOUT_MS` | `300000` | `open-sse/executors/cursor.ts` | Stream idle timeout (ms) for the Cursor executor. | | `CURSOR_STREAM_TIMEOUT_MS` | `300000` | `open-sse/executors/cursor.ts` | Stream idle timeout (ms) for the Cursor executor. |
| `CURSOR_STATE_DB_PATH` | _(probed)_ | `open-sse/utils/cursorVersionDetector.ts` | Override the Cursor state DB lookup used for version detection. | | `CURSOR_STATE_DB_PATH` | _(probed)_ | `open-sse/utils/cursorVersionDetector.ts` | Override the Cursor state DB lookup used for version detection. |
| `CURSOR_TOKEN` | _(unset)_ | `scripts/cursor-tap.cjs` | Direct Cursor bearer token used by developer tooling. | | `CURSOR_TOKEN` | _(unset)_ | `scripts/ad-hoc/cursor-tap.cjs` | Direct Cursor bearer token used by developer tooling. |
| `OMNIROUTE_LOG_REQUEST_SHAPE` | enabled (`!== "0"`) | `src/app/api/v1/chat/completions/route.ts` | Log content-type/length markers for large chat payloads. Set `"0"` to silence. | | `OMNIROUTE_LOG_REQUEST_SHAPE` | enabled (`!== "0"`) | `src/app/api/v1/chat/completions/route.ts` | Log content-type/length markers for large chat payloads. Set `"0"` to silence. |
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. | | `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). | | `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
@@ -794,28 +794,28 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
## 26. Test & E2E Harness ## 26. Test & E2E Harness
Used by `scripts/run-next-playwright.mjs`, `scripts/smoke-electron-packaged.mjs`, Used by `scripts/dev/run-next-playwright.mjs`, `scripts/dev/smoke-electron-packaged.mjs`,
`scripts/run-ecosystem-tests.mjs`, and `scripts/uninstall.mjs`. Leave every `scripts/dev/run-ecosystem-tests.mjs`, and `scripts/build/uninstall.mjs`. Leave every
value below unset in production deployments. value below unset in production deployments.
| Variable | Default | Source File | Description | | Variable | Default | Source File | Description |
| ------------------------------------- | -------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | | ------------------------------------- | -------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| `OMNIROUTE_E2E_BOOTSTRAP_MODE` | `auth` | `scripts/run-next-playwright.mjs` | E2E bootstrap mode (`auth`, `fresh`, `reuse`) for the Playwright runner. | | `OMNIROUTE_E2E_BOOTSTRAP_MODE` | `auth` | `scripts/dev/run-next-playwright.mjs` | E2E bootstrap mode (`auth`, `fresh`, `reuse`) for the Playwright runner. |
| `OMNIROUTE_E2E_PASSWORD` | falls back to `INITIAL_PASSWORD` | `scripts/run-next-playwright.mjs` | Admin password injected into the Playwright environment. | | `OMNIROUTE_E2E_PASSWORD` | falls back to `INITIAL_PASSWORD` | `scripts/dev/run-next-playwright.mjs` | Admin password injected into the Playwright environment. |
| `OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK` | `true` | `scripts/run-next-playwright.mjs` | Disable the local healthcheck poll during Playwright runs. | | `OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | Disable the local healthcheck poll during Playwright runs. |
| `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | `true` | `scripts/run-next-playwright.mjs` | Disable the OAuth token healthcheck loop during tests. | | `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | `true` | `scripts/dev/run-next-playwright.mjs` | Disable the OAuth token healthcheck loop during tests. |
| `OMNIROUTE_HIDE_HEALTHCHECK_LOGS` | `true` | `scripts/run-next-playwright.mjs` | Silence healthcheck noise in Playwright stdout. | | `OMNIROUTE_HIDE_HEALTHCHECK_LOGS` | `true` | `scripts/dev/run-next-playwright.mjs` | Silence healthcheck noise in Playwright stdout. |
| `OMNIROUTE_PLAYWRIGHT_SKIP_BUILD` | `0` | `scripts/run-next-playwright.mjs` | Skip the Next.js production build before Playwright starts (CI optimization). | | `OMNIROUTE_PLAYWRIGHT_SKIP_BUILD` | `0` | `scripts/dev/run-next-playwright.mjs` | Skip the Next.js production build before Playwright starts (CI optimization). |
| `OMNIROUTE_SKIP_UNINSTALL_HOOK` | `0` | `scripts/uninstall.mjs` | Skip the OmniRoute uninstall hook (used by CI to keep `node_modules` intact). | | `OMNIROUTE_SKIP_UNINSTALL_HOOK` | `0` | `scripts/build/uninstall.mjs` | Skip the OmniRoute uninstall hook (used by CI to keep `node_modules` intact). |
| `ECOSYSTEM_SERVER_WAIT_MS` | `180000` | `scripts/run-ecosystem-tests.mjs` | Wait time (ms) for the server to become healthy before running ecosystem/protocol tests. | | `ECOSYSTEM_SERVER_WAIT_MS` | `180000` | `scripts/dev/run-ecosystem-tests.mjs` | Wait time (ms) for the server to become healthy before running ecosystem/protocol tests. |
| `ELECTRON_SMOKE_URL` | `http://127.0.0.1:20128/login` | `scripts/smoke-electron-packaged.mjs` | URL the Electron smoke harness expects the packaged app to serve. | | `ELECTRON_SMOKE_URL` | `http://127.0.0.1:20128/login` | `scripts/dev/smoke-electron-packaged.mjs` | URL the Electron smoke harness expects the packaged app to serve. |
| `ELECTRON_SMOKE_TIMEOUT_MS` | `45000` | `scripts/smoke-electron-packaged.mjs` | Total timeout (ms) before the smoke harness gives up. | | `ELECTRON_SMOKE_TIMEOUT_MS` | `45000` | `scripts/dev/smoke-electron-packaged.mjs` | Total timeout (ms) before the smoke harness gives up. |
| `ELECTRON_SMOKE_SETTLE_MS` | `2000` | `scripts/smoke-electron-packaged.mjs` | Settle window (ms) after the page loads. | | `ELECTRON_SMOKE_SETTLE_MS` | `2000` | `scripts/dev/smoke-electron-packaged.mjs` | Settle window (ms) after the page loads. |
| `ELECTRON_SMOKE_APP_EXECUTABLE` | _(auto)_ | `scripts/smoke-electron-packaged.mjs` | Explicit path to the packaged Electron executable. | | `ELECTRON_SMOKE_APP_EXECUTABLE` | _(auto)_ | `scripts/dev/smoke-electron-packaged.mjs` | Explicit path to the packaged Electron executable. |
| `ELECTRON_SMOKE_DATA_DIR` | _(tmpdir)_ | `scripts/smoke-electron-packaged.mjs` | Data directory for the Electron smoke run. | | `ELECTRON_SMOKE_DATA_DIR` | _(tmpdir)_ | `scripts/dev/smoke-electron-packaged.mjs` | Data directory for the Electron smoke run. |
| `ELECTRON_SMOKE_KEEP_DATA` | `0` | `scripts/smoke-electron-packaged.mjs` | Set `1` to preserve the smoke data directory after the run. | | `ELECTRON_SMOKE_KEEP_DATA` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | Set `1` to preserve the smoke data directory after the run. |
| `ELECTRON_SMOKE_STREAM_LOGS` | `0` | `scripts/smoke-electron-packaged.mjs` | Set `1` to stream Electron logs to stdout during the run. | | `ELECTRON_SMOKE_STREAM_LOGS` | `0` | `scripts/dev/smoke-electron-packaged.mjs` | Set `1` to stream Electron logs to stdout during the run. |
| `CLI_DEVIN_BIN` | _(PATH lookup)_ | `open-sse/executors/devin-cli.ts` | Override the Devin CLI binary path. | | `CLI_DEVIN_BIN` | _(PATH lookup)_ | `open-sse/executors/devin-cli.ts` | Override the Devin CLI binary path. |
### Docs translation pipeline ### Docs translation pipeline

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
"pack": "npm run prepare:bundle && electron-builder --dir" "pack": "npm run prepare:bundle && electron-builder --dir"
}, },
"dependencies": { "dependencies": {
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.10.0",
"electron-updater": "^6.8.5" "electron-updater": "^6.8.5"
}, },
"devDependencies": { "devDependencies": {
@@ -33,7 +33,6 @@
"electron-builder": "^26.10.0" "electron-builder": "^26.10.0"
}, },
"overrides": { "overrides": {
"@xmldom/xmldom": "^0.9.10",
"plist": "^4.0.0" "plist": "^4.0.0"
}, },
"build": { "build": {

View File

@@ -142,7 +142,7 @@ function clampMaxTokens(value: unknown): number {
return Math.max(1, Math.min(Math.floor(numeric), MAX_COMMAND_CODE_TOKENS)); return Math.max(1, Math.min(Math.floor(numeric), MAX_COMMAND_CODE_TOKENS));
} }
function buildCommandCodeBody(model: string, body: unknown): JsonRecord { function buildCommandCodeBody(model: string, body: unknown, stream: boolean): JsonRecord {
const input = isRecord(body) ? body : {}; const input = isRecord(body) ? body : {};
const converted = convertMessages(input.messages); const converted = convertMessages(input.messages);
const explicitSystem = typeof input.system === "string" ? input.system : ""; const explicitSystem = typeof input.system === "string" ? input.system : "";
@@ -169,7 +169,7 @@ function buildCommandCodeBody(model: string, body: unknown): JsonRecord {
tools: convertTools(input.tools), tools: convertTools(input.tools),
system, system,
max_tokens: clampMaxTokens(input.max_tokens ?? input.max_completion_tokens), max_tokens: clampMaxTokens(input.max_tokens ?? input.max_completion_tokens),
stream: false, stream,
}, },
}; };
} }
@@ -512,7 +512,7 @@ export class CommandCodeExecutor extends BaseExecutor {
}; };
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders); mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const transformedBody = buildCommandCodeBody(model, body); const transformedBody = buildCommandCodeBody(model, body, stream);
const url = this.buildUrl(); const url = this.buildUrl();
const upstream = await fetch(url, { const upstream = await fetch(url, {
method: "POST", method: "POST",

169
package-lock.json generated
View File

@@ -18,9 +18,9 @@
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@ngrok/ngrok": "^1.7.0", "@ngrok/ngrok": "^1.7.0",
"@swc/helpers": "0.5.21", "@swc/helpers": "0.5.21",
"axios": "^1.16.0", "axios": "^1.16.1",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.10.0",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
"express": "^5.2.1", "express": "^5.2.1",
"fetch-socks": "^1.3.3", "fetch-socks": "^1.3.3",
@@ -29,17 +29,17 @@
"http-proxy-middleware": "^4.0.0", "http-proxy-middleware": "^4.0.0",
"https-proxy-agent": "^9.0.0", "https-proxy-agent": "^9.0.0",
"ioredis": "^5.10.1", "ioredis": "^5.10.1",
"isomorphic-dompurify": "^3.12.0", "isomorphic-dompurify": "^3.13.0",
"jose": "^6.2.3", "jose": "^6.2.3",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lucide-react": "^1.14.0", "lucide-react": "^1.16.0",
"marked": "^18.0.3", "marked": "^18.0.3",
"mermaid": "^11.14.0", "mermaid": "^11.15.0",
"monaco-editor": "^0.55.1", "monaco-editor": "^0.55.1",
"next": "^16.2.6", "next": "^16.2.6",
"next-intl": "^4.11.1", "next-intl": "^4.12.0",
"node-machine-id": "^1.1.12", "node-machine-id": "^1.1.12",
"open": "^11.0.0", "open": "^11.0.0",
"ora": "^9.4.0", "ora": "^9.4.0",
@@ -53,7 +53,7 @@
"recharts": "^3.8.1", "recharts": "^3.8.1",
"selfsigned": "^5.5.0", "selfsigned": "^5.5.0",
"tls-client-node": "^0.1.13", "tls-client-node": "^0.1.13",
"tsx": "^4.21.0", "tsx": "^4.21.1",
"undici": "^8.2.0", "undici": "^8.2.0",
"uuid": "^14.0.0", "uuid": "^14.0.0",
"wreq-js": "^2.3.0", "wreq-js": "^2.3.0",
@@ -67,14 +67,14 @@
"omniroute-reset-password": "bin/reset-password.mjs" "omniroute-reset-password": "bin/reset-password.mjs"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.59.1", "@playwright/test": "^1.60.0",
"@tailwindcss/postcss": "^4.3.0", "@tailwindcss/postcss": "^4.3.0",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"@types/keytar": "^4.4.2", "@types/keytar": "^4.4.2",
"@types/node": "^25.6.2", "@types/node": "^25.7.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
@@ -91,9 +91,9 @@
"prettier": "^3.8.3", "prettier": "^3.8.3",
"tailwindcss": "^4.3.0", "tailwindcss": "^4.3.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.2", "typescript-eslint": "^8.59.3",
"vitest": "^4.1.5", "vitest": "^4.1.6",
"wait-on": "^9.0.5", "wait-on": "^9.0.10",
"wtfnode": "^0.10.1" "wtfnode": "^0.10.1"
}, },
"engines": { "engines": {
@@ -1430,18 +1430,18 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@formatjs/icu-messageformat-parser": { "node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.7", "version": "3.5.8",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.7.tgz", "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.8.tgz",
"integrity": "sha512-wJxRZ+SiUCIMTL86bQlZU9bEKDQqqvgk2ezQ1BySUdWRfHqOzj4IKUVFeUZKS9w58M4e7wMSG0Sl86LAPb7Qww==", "integrity": "sha512-uZLvzLFN7iV2l8cbDdROwgKGtdELeLI4bpnsuz1DnyscHDxn8TdDE0anHzcfjtWK66XYCllGLV3Mi3CYcEPg/g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@formatjs/icu-skeleton-parser": "2.1.7" "@formatjs/icu-skeleton-parser": "2.1.8"
} }
}, },
"node_modules/@formatjs/icu-skeleton-parser": { "node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.7", "version": "2.1.8",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.7.tgz", "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.8.tgz",
"integrity": "sha512-cIw1SFP0bi0CUBiJ2jzp99ws3OJNQDfStcHq9Z0iHWzItmiIikihFO+npR8C80yDlp7ZuBCLXCcKjgWjHicksA==", "integrity": "sha512-iX5i0O15gPf69l1WqmLFYwn7wq53lauvytvWFnHamIfX/5Ta56gpFj6fdeHRcKTV58IhrKv8QOvWfTYZYm7f+g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@formatjs/intl-localematcher": { "node_modules/@formatjs/intl-localematcher": {
@@ -5626,16 +5626,42 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.16.0", "version": "1.16.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.16.0", "follow-redirects": "^1.16.0",
"form-data": "^4.0.5", "form-data": "^4.0.5",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0" "proxy-from-env": "^2.1.0"
} }
}, },
"node_modules/axios/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -5720,9 +5746,9 @@
} }
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
"version": "12.9.0", "version": "12.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz",
"integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==", "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5730,7 +5756,7 @@
"prebuild-install": "^7.1.1" "prebuild-install": "^7.1.1"
}, },
"engines": { "engines": {
"node": "20.x || 22.x || 23.x || 24.x || 25.x" "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
} }
}, },
"node_modules/bidi-js": { "node_modules/bidi-js": {
@@ -7358,9 +7384,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.4.2", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.3.tgz",
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", "integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==",
"license": "(MPL-2.0 OR Apache-2.0)", "license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": { "optionalDependencies": {
"@types/trusted-types": "^2.0.7" "@types/trusted-types": "^2.0.7"
@@ -8895,6 +8921,7 @@
"version": "4.13.7", "version": "4.13.7",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
"integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"resolve-pkg-maps": "^1.0.0" "resolve-pkg-maps": "^1.0.0"
@@ -9366,9 +9393,9 @@
} }
}, },
"node_modules/icu-minify": { "node_modules/icu-minify": {
"version": "4.11.1", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.11.1.tgz", "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.12.0.tgz",
"integrity": "sha512-C0tsPVuvyNp+++qWJP+mty/KLLStjerOZqu3W1xWLJkChEDbDi9Taoj6blK7L/onxbuVzwgH6k9Sf+rOV6lOvw==", "integrity": "sha512-zDmM05uav3t3+kxSfRrNlmyXOdj2b+uHA+p04CG32eJabtaHbugXujuL+YfRkwP9joAnf0Uh+RMGCKD5NLa5rQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -9499,19 +9526,19 @@
} }
}, },
"node_modules/intl-messageformat": { "node_modules/intl-messageformat": {
"version": "11.2.4", "version": "11.2.5",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.4.tgz", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.5.tgz",
"integrity": "sha512-iKP6+uJXn+XcfRgYfGPE3+mqCoODV2vATrXDLo/YkYgIdelJHJPBEbc0GZThipAYPuk+8QJFiPgOfblU085ABg==", "integrity": "sha512-zaROHiUsnlSFXVypU54AsQuAm3DLmmSH8KfDhiUuG1XZ9NTQ4o3xlxIJYVNmeWAklyp3CWg0lhexNUnee8PsYQ==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"@formatjs/fast-memoize": "3.1.4", "@formatjs/fast-memoize": "3.1.5",
"@formatjs/icu-messageformat-parser": "3.5.7" "@formatjs/icu-messageformat-parser": "3.5.8"
} }
}, },
"node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": { "node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": {
"version": "3.1.4", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.4.tgz", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz",
"integrity": "sha512-Lbke1aOrsygKKR09Ux0NrZgbTqpDmiwXOgzyDOJ8Owr1zd5qOKTauf62hH+Seeku3ju77rHWH9I5SfX2CN0vuA==", "integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ioredis": { "node_modules/ioredis": {
@@ -9539,9 +9566,9 @@
} }
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "10.1.1", "version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.1.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw==", "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 12" "node": ">= 12"
@@ -10170,12 +10197,12 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/isomorphic-dompurify": { "node_modules/isomorphic-dompurify": {
"version": "3.12.0", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-3.12.0.tgz", "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-3.13.0.tgz",
"integrity": "sha512-8n+j+6ypTHvriJwFOQ2qusQ6bzGjZVcR3jbe1pBpLcGI1dn4WIl0ctLBngqE5QttquQBAlKXwJeTMw+X7x7qKw==", "integrity": "sha512-QzgzjAGJN0QoOpLWx7mkMhhTQvQXsBpS6oEi2Wy58mEWwrvaRJrx5hrVEdsM70nNKOwHCHj3bwYkwI+HFd3Khg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dompurify": "^3.4.2", "dompurify": "^3.4.3",
"jsdom": "^29.1.1" "jsdom": "^29.1.1"
}, },
"engines": { "engines": {
@@ -11088,9 +11115,9 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "1.14.0", "version": "1.16.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz",
"integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -12101,9 +12128,9 @@
} }
}, },
"node_modules/next-intl": { "node_modules/next-intl": {
"version": "4.11.1", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.11.1.tgz", "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.12.0.tgz",
"integrity": "sha512-s32lFFLXkxrW6fy+4IVaGD5J8xPpbEDFLfBbXV73CTbHAGhOGMjYN4/rftdsKOQ44AnPhnZ5Et+ZNMr5tRpsqA==", "integrity": "sha512-v8KpppWG0yLLlChJ3Of6uoPew9LeRDBAtY6vpJmF7YJmBZlHEzzoEL4w1g1dAU+VleEPNoXNm9hg1eEsKWV5hw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -12115,11 +12142,11 @@
"@formatjs/intl-localematcher": "^0.8.1", "@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1", "@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2", "@swc/core": "^1.15.2",
"icu-minify": "^4.11.1", "icu-minify": "^4.12.0",
"negotiator": "^1.0.0", "negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.11.1", "next-intl-swc-plugin-extractor": "^4.12.0",
"po-parser": "^2.1.1", "po-parser": "^2.1.1",
"use-intl": "^4.11.1" "use-intl": "^4.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -12132,9 +12159,9 @@
} }
}, },
"node_modules/next-intl-swc-plugin-extractor": { "node_modules/next-intl-swc-plugin-extractor": {
"version": "4.11.1", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.11.1.tgz", "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.12.0.tgz",
"integrity": "sha512-jHKGij7NoYccy2y54+e/wHVMoRgNt4h/Kn0XS9c4GbKu3KgJyANLUN8sFcDixv6sqz4V2kh6CTWgrkIidQksUg==", "integrity": "sha512-jUxVEu1Nryjt4YgaDktSys7ioOgQfcNPF/SF2dbPNxbVb6U+P1INRgHeCVN+EC59H2rnTFIQwbddmOCrUWFr3g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/next/node_modules/@swc/helpers": { "node_modules/next/node_modules/@swc/helpers": {
@@ -12910,9 +12937,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.10", "version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -13527,6 +13554,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
@@ -15003,13 +15031,12 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": { "node_modules/tsx": {
"version": "4.21.0", "version": "4.21.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.1.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "integrity": "sha512-5QE2Q04cN1u0993w0LT5rPw3faZqZU1fFn1mGE0pV53N1Dn7c+QFFxQu1mBeSgeOXwFyTicZw02wVgp3Tb5cAQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "~0.27.0", "esbuild": "~0.27.0"
"get-tsconfig": "^4.7.5"
}, },
"bin": { "bin": {
"tsx": "dist/cli.mjs" "tsx": "dist/cli.mjs"
@@ -15422,9 +15449,9 @@
} }
}, },
"node_modules/use-intl": { "node_modules/use-intl": {
"version": "4.11.1", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.11.1.tgz", "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.12.0.tgz",
"integrity": "sha512-/dqWSqUSbVMzC+fdy7io8enhGYHeGeHK1bFhTLrp0ZblqdzY4FkE+tkffW6IfCauqaIA2/z4DQae4XEn93+raw==", "integrity": "sha512-r+qVb7UI1+kiOhjYsmsNUCY+jrnjVopwGeFlmMyQj4YInlwZzgMeMSv9n8MqnWWy77HL5BVM8K2WgX50SbtcpA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -15435,7 +15462,7 @@
"dependencies": { "dependencies": {
"@formatjs/fast-memoize": "^3.1.0", "@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5", "@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.11.1", "icu-minify": "^4.12.0",
"intl-messageformat": "^11.1.0" "intl-messageformat": "^11.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -65,9 +65,9 @@
"scripts": { "scripts": {
"dev": "node scripts/dev/run-next.mjs dev", "dev": "node scripts/dev/run-next.mjs dev",
"prebuild:docs": "node scripts/docs/generate-docs-index.mjs && node scripts/docs/gen-openapi-module.mjs", "prebuild:docs": "node scripts/docs/generate-docs-index.mjs && node scripts/docs/gen-openapi-module.mjs",
"gen:provider-reference": "node --import tsx/esm scripts/docs/gen-provider-reference.ts", "gen:provider-reference": "node --import tsx scripts/docs/gen-provider-reference.ts",
"build": "node scripts/build/build-next-isolated.mjs", "build": "node scripts/build/build-next-isolated.mjs",
"build:cli": "node --import tsx/esm scripts/build/prepublish.ts", "build:cli": "node --import tsx scripts/build/prepublish.ts",
"start": "node scripts/dev/run-next.mjs start", "start": "node scripts/dev/run-next.mjs start",
"lint": "eslint .", "lint": "eslint .",
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"", "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"",
@@ -76,11 +76,11 @@
"electron:build:mac": "npm run build && cd electron && npm run build:mac", "electron:build:mac": "npm run build && cd electron && npm run build:mac",
"electron:build:linux": "npm run build && cd electron && npm run build:linux", "electron:build:linux": "npm run build && cd electron && npm run build:linux",
"electron:smoke:packaged": "node scripts/dev/smoke-electron-packaged.mjs", "electron:smoke:packaged": "node scripts/dev/smoke-electron-packaged.mjs",
"test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts", "test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test --test-concurrency=10 tests/unit/*.test.ts",
"test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts", "test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test --test-concurrency=10 tests/unit/*.test.ts",
"test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts", "test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/plan3-p0.test.ts",
"test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts", "test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/fixes-p1.test.ts",
"test:security": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts", "test:security": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/security-fase01.test.ts",
"check:cycles": "node scripts/check/check-cycles.mjs", "check:cycles": "node scripts/check/check-cycles.mjs",
"check:route-validation:t06": "node scripts/check/check-route-validation.mjs", "check:route-validation:t06": "node scripts/check/check-route-validation.mjs",
"check:any-budget:t11": "node scripts/check/check-t11-any-budget.mjs", "check:any-budget:t11": "node scripts/check/check-t11-any-budget.mjs",
@@ -97,21 +97,21 @@
"i18n:sync-ui": "node scripts/i18n/sync-ui-keys.mjs", "i18n:sync-ui": "node scripts/i18n/sync-ui-keys.mjs",
"i18n:sync-ui:dry": "node scripts/i18n/sync-ui-keys.mjs --dry-run", "i18n:sync-ui:dry": "node scripts/i18n/sync-ui-keys.mjs --dry-run",
"i18n:check-ui-coverage": "node scripts/i18n/check-ui-keys-coverage.mjs", "i18n:check-ui-coverage": "node scripts/i18n/check-ui-keys-coverage.mjs",
"check:node-runtime": "node --import tsx/esm scripts/check/check-supported-node-runtime.ts", "check:node-runtime": "node --import tsx scripts/check/check-supported-node-runtime.ts",
"check:pack-artifact": "node --import tsx/esm scripts/build/validate-pack-artifact.ts", "check:pack-artifact": "node --import tsx scripts/build/validate-pack-artifact.ts",
"audit:deps": "npm audit --audit-level=moderate && npm run audit:electron", "audit:deps": "npm audit --audit-level=moderate && npm run audit:electron",
"audit:electron": "npm --prefix electron audit --audit-level=moderate", "audit:electron": "npm --prefix electron audit --audit-level=moderate",
"typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json", "typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json",
"typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json", "typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json",
"backfill-aggregation": "node --import tsx/esm src/scripts/backfillAggregation.ts", "backfill-aggregation": "node --import tsx src/scripts/backfillAggregation.ts",
"env:sync": "node scripts/dev/sync-env.mjs", "env:sync": "node scripts/dev/sync-env.mjs",
"test:integration": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-force-exit --test-concurrency=1 tests/integration/*.test.ts", "test:integration": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test --test-force-exit --test-concurrency=1 tests/integration/*.test.ts",
"test:e2e": "node scripts/dev/run-playwright-tests.mjs test tests/e2e/*.spec.ts", "test:e2e": "node scripts/dev/run-playwright-tests.mjs test tests/e2e/*.spec.ts",
"test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs", "test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run --config vitest.mcp.config.ts", "test:vitest": "vitest run --config vitest.mcp.config.ts",
"test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs", "test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs",
"test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --import tsx/esm --test --test-concurrency=1 tests/unit/*.test.ts", "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --import tsx --test --test-concurrency=1 tests/unit/*.test.ts",
"test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts", "test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx --test tests/unit/*.test.ts",
"coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov", "coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
"coverage:summary": "node scripts/check/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60", "coverage:summary": "node scripts/check/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60",
"check:pr-test-policy": "node scripts/check/check-pr-test-policy.mjs", "check:pr-test-policy": "node scripts/check/check-pr-test-policy.mjs",
@@ -131,9 +131,9 @@
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@ngrok/ngrok": "^1.7.0", "@ngrok/ngrok": "^1.7.0",
"@swc/helpers": "0.5.21", "@swc/helpers": "0.5.21",
"axios": "^1.16.0", "axios": "^1.16.1",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.10.0",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
"express": "^5.2.1", "express": "^5.2.1",
"fetch-socks": "^1.3.3", "fetch-socks": "^1.3.3",
@@ -142,17 +142,17 @@
"http-proxy-middleware": "^4.0.0", "http-proxy-middleware": "^4.0.0",
"https-proxy-agent": "^9.0.0", "https-proxy-agent": "^9.0.0",
"ioredis": "^5.10.1", "ioredis": "^5.10.1",
"isomorphic-dompurify": "^3.12.0", "isomorphic-dompurify": "^3.13.0",
"jose": "^6.2.3", "jose": "^6.2.3",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lucide-react": "^1.14.0", "lucide-react": "^1.16.0",
"marked": "^18.0.3", "marked": "^18.0.3",
"mermaid": "^11.14.0", "mermaid": "^11.15.0",
"monaco-editor": "^0.55.1", "monaco-editor": "^0.55.1",
"next": "^16.2.6", "next": "^16.2.6",
"next-intl": "^4.11.1", "next-intl": "^4.12.0",
"node-machine-id": "^1.1.12", "node-machine-id": "^1.1.12",
"open": "^11.0.0", "open": "^11.0.0",
"ora": "^9.4.0", "ora": "^9.4.0",
@@ -166,7 +166,7 @@
"recharts": "^3.8.1", "recharts": "^3.8.1",
"selfsigned": "^5.5.0", "selfsigned": "^5.5.0",
"tls-client-node": "^0.1.13", "tls-client-node": "^0.1.13",
"tsx": "^4.21.0", "tsx": "^4.21.1",
"undici": "^8.2.0", "undici": "^8.2.0",
"uuid": "^14.0.0", "uuid": "^14.0.0",
"wreq-js": "^2.3.0", "wreq-js": "^2.3.0",
@@ -179,14 +179,14 @@
"keytar": "^7.9.0" "keytar": "^7.9.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.59.1", "@playwright/test": "^1.60.0",
"@tailwindcss/postcss": "^4.3.0", "@tailwindcss/postcss": "^4.3.0",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"@types/keytar": "^4.4.2", "@types/keytar": "^4.4.2",
"@types/node": "^25.6.2", "@types/node": "^25.7.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
@@ -203,9 +203,9 @@
"prettier": "^3.8.3", "prettier": "^3.8.3",
"tailwindcss": "^4.3.0", "tailwindcss": "^4.3.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.2", "typescript-eslint": "^8.59.3",
"vitest": "^4.1.5", "vitest": "^4.1.6",
"wait-on": "^9.0.5", "wait-on": "^9.0.10",
"wtfnode": "^0.10.1" "wtfnode": "^0.10.1"
}, },
"lint-staged": { "lint-staged": {
@@ -228,14 +228,8 @@
] ]
}, },
"overrides": { "overrides": {
"lodash-es": "^4.18.1", "dompurify": "^3.4.3",
"dompurify": "^3.4.2", "postcss": "^8.5.14",
"path-to-regexp": "^8.4.0", "ip-address": "10.2.0"
"postcss": "^8.5.10",
"ip-address": "10.1.1",
"hono": "^4.12.18",
"@hono/node-server": "^1.19.13",
"react": "$react",
"react-dom": "$react-dom"
} }
} }

View File

@@ -22,13 +22,13 @@
"src/shared/contracts/", "src/shared/contracts/",
"src/shared/utils/nodeRuntimeSupport.ts", "src/shared/utils/nodeRuntimeSupport.ts",
".env.example", ".env.example",
"scripts/postinstall.mjs", "scripts/build/postinstall.mjs",
"scripts/postinstallSupport.mjs", "scripts/build/postinstallSupport.mjs",
"scripts/responses-ws-proxy.mjs", "scripts/dev/responses-ws-proxy.mjs",
"scripts/check-supported-node-runtime.ts", "scripts/check/check-supported-node-runtime.ts",
"scripts/sync-env.mjs", "scripts/dev/sync-env.mjs",
"scripts/native-binary-compat.mjs", "scripts/build/native-binary-compat.mjs",
"scripts/build-next-isolated.mjs", "scripts/build/build-next-isolated.mjs",
"README.md", "README.md",
"LICENSE" "LICENSE"
], ],
@@ -61,54 +61,54 @@
}, },
"homepage": "https://omniroute.online", "homepage": "https://omniroute.online",
"scripts": { "scripts": {
"dev": "node scripts/run-next.mjs dev", "dev": "node scripts/dev/run-next.mjs dev",
"prebuild:docs": "node scripts/generate-docs-index.mjs", "prebuild:docs": "node scripts/docs/generate-docs-index.mjs",
"build": "node scripts/build-next-isolated.mjs", "build": "node scripts/build/build-next-isolated.mjs",
"build:cli": "node --import tsx/esm scripts/prepublish.ts", "build:cli": "node --import tsx scripts/build/prepublish.ts",
"start": "node scripts/run-next.mjs start", "start": "node scripts/dev/run-next.mjs start",
"lint": "eslint .", "lint": "eslint .",
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"", "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"",
"electron:build": "npm run build && cd electron && npm run build", "electron:build": "npm run build && cd electron && npm run build",
"electron:build:win": "npm run build && cd electron && npm run build:win", "electron:build:win": "npm run build && cd electron && npm run build:win",
"electron:build:mac": "npm run build && cd electron && npm run build:mac", "electron:build:mac": "npm run build && cd electron && npm run build:mac",
"electron:build:linux": "npm run build && cd electron && npm run build:linux", "electron:build:linux": "npm run build && cd electron && npm run build:linux",
"electron:smoke:packaged": "node scripts/smoke-electron-packaged.mjs", "electron:smoke:packaged": "node scripts/dev/smoke-electron-packaged.mjs",
"test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts", "test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test --test-concurrency=10 tests/unit/*.test.ts",
"test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts", "test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test --test-concurrency=10 tests/unit/*.test.ts",
"test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts", "test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/plan3-p0.test.ts",
"test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts", "test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/fixes-p1.test.ts",
"test:security": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts", "test:security": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --test tests/unit/security-fase01.test.ts",
"check:cycles": "node scripts/check-cycles.mjs", "check:cycles": "node scripts/check/check-cycles.mjs",
"check:route-validation:t06": "node scripts/check-route-validation.mjs", "check:route-validation:t06": "node scripts/check/check-route-validation.mjs",
"check:any-budget:t11": "node scripts/check-t11-any-budget.mjs", "check:any-budget:t11": "node scripts/check/check-t11-any-budget.mjs",
"check:docs-sync": "node scripts/check-docs-sync.mjs", "check:docs-sync": "node scripts/check/check-docs-sync.mjs",
"check:node-runtime": "node --import tsx/esm scripts/check-supported-node-runtime.ts", "check:node-runtime": "node --import tsx scripts/check/check-supported-node-runtime.ts",
"check:pack-artifact": "node --import tsx/esm scripts/validate-pack-artifact.ts", "check:pack-artifact": "node --import tsx scripts/build/validate-pack-artifact.ts",
"audit:deps": "npm audit --audit-level=moderate && npm run audit:electron", "audit:deps": "npm audit --audit-level=moderate && npm run audit:electron",
"audit:electron": "npm --prefix electron audit --audit-level=moderate", "audit:electron": "npm --prefix electron audit --audit-level=moderate",
"typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json", "typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json",
"typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json", "typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json",
"backfill-aggregation": "node --import tsx/esm src/scripts/backfillAggregation.ts", "backfill-aggregation": "node --import tsx src/scripts/backfillAggregation.ts",
"env:sync": "node scripts/sync-env.mjs", "env:sync": "node scripts/dev/sync-env.mjs",
"test:integration": "node --import tsx/esm --test tests/integration/*.test.ts", "test:integration": "node --import tsx --test tests/integration/*.test.ts",
"test:e2e": "node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts", "test:e2e": "node scripts/dev/run-playwright-tests.mjs test tests/e2e/*.spec.ts",
"test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs", "test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run --config vitest.mcp.config.ts", "test:vitest": "vitest run --config vitest.mcp.config.ts",
"test:ecosystem": "node scripts/run-ecosystem-tests.mjs", "test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs",
"test:coverage": "c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts", "test:coverage": "c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx --test --test-concurrency=10 tests/unit/*.test.ts",
"test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts", "test:coverage:legacy": "c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx --test tests/unit/*.test.ts",
"coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov", "coverage:report": "c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
"coverage:summary": "node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60", "coverage:summary": "node scripts/check/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60",
"check:pr-test-policy": "node scripts/check-pr-test-policy.mjs", "check:pr-test-policy": "node scripts/check/check-pr-test-policy.mjs",
"coverage:report:legacy": "c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary", "coverage:report:legacy": "c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary",
"test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e", "test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e",
"check": "npm run lint && npm run test", "check": "npm run lint && npm run test",
"prepublishOnly": "npm run build:cli && npm run check:pack-artifact", "prepublishOnly": "npm run build:cli && npm run check:pack-artifact",
"postinstall": "node scripts/postinstall.mjs", "postinstall": "node scripts/build/postinstall.mjs",
"uninstall": "node scripts/uninstall.mjs", "uninstall": "node scripts/build/uninstall.mjs",
"uninstall:full": "node scripts/uninstall.mjs --full", "uninstall:full": "node scripts/build/uninstall.mjs --full",
"prepare": "husky", "prepare": "husky",
"system-info": "node scripts/system-info.mjs" "system-info": "node scripts/dev/system-info.mjs"
}, },
"dependencies": { "dependencies": {
"@lobehub/icons": "^5.8.0", "@lobehub/icons": "^5.8.0",
@@ -116,26 +116,26 @@
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@ngrok/ngrok": "^1.7.0", "@ngrok/ngrok": "^1.7.0",
"@swc/helpers": "0.5.21", "@swc/helpers": "0.5.21",
"axios": "^1.16.0", "axios": "^1.16.1",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.10.0",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
"express": "^5.2.1", "express": "^5.2.1",
"fetch-socks": "^1.3.3", "fetch-socks": "^1.3.3",
"fuse.js": "^7.3.0", "fuse.js": "^7.3.0",
"http-proxy-middleware": "^3.0.5", "http-proxy-middleware": "^3.0.5",
"https-proxy-agent": "^9.0.0", "https-proxy-agent": "^9.0.0",
"isomorphic-dompurify": "^3.12.0", "isomorphic-dompurify": "^3.13.0",
"jose": "^6.2.3", "jose": "^6.2.3",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lucide-react": "^1.14.0", "lucide-react": "^1.16.0",
"marked": "^18.0.3", "marked": "^18.0.3",
"mermaid": "^11.14.0", "mermaid": "^11.15.0",
"monaco-editor": "^0.55.1", "monaco-editor": "^0.55.1",
"next": "^16.2.6", "next": "^16.2.6",
"next-intl": "^4.11.1", "next-intl": "^4.12.0",
"node-machine-id": "^1.1.12", "node-machine-id": "^1.1.12",
"open": "^11.0.0", "open": "^11.0.0",
"ora": "^9.4.0", "ora": "^9.4.0",
@@ -149,7 +149,7 @@
"recharts": "^3.8.1", "recharts": "^3.8.1",
"selfsigned": "^5.5.0", "selfsigned": "^5.5.0",
"tls-client-node": "^0.1.13", "tls-client-node": "^0.1.13",
"tsx": "^4.21.0", "tsx": "^4.21.1",
"undici": "^8.2.0", "undici": "^8.2.0",
"uuid": "^14.0.0", "uuid": "^14.0.0",
"wreq-js": "^2.3.0", "wreq-js": "^2.3.0",
@@ -162,14 +162,14 @@
"keytar": "^7.9.0" "keytar": "^7.9.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.59.1", "@playwright/test": "^1.60.0",
"@tailwindcss/postcss": "^4.3.0", "@tailwindcss/postcss": "^4.3.0",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"@types/keytar": "^4.4.2", "@types/keytar": "^4.4.2",
"@types/node": "^25.6.2", "@types/node": "^25.7.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
@@ -187,9 +187,9 @@
"prettier": "^3.8.3", "prettier": "^3.8.3",
"tailwindcss": "^4.3.0", "tailwindcss": "^4.3.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.2", "typescript-eslint": "^8.59.3",
"vitest": "^4.1.5", "vitest": "^4.1.6",
"wait-on": "^9.0.5", "wait-on": "^9.0.10",
"wtfnode": "^0.10.1" "wtfnode": "^0.10.1"
}, },
"lint-staged": { "lint-staged": {
@@ -212,14 +212,8 @@
] ]
}, },
"overrides": { "overrides": {
"lodash-es": "^4.18.1", "dompurify": "^3.4.3",
"dompurify": "^3.4.2", "postcss": "^8.5.14",
"path-to-regexp": "^8.4.0", "ip-address": "10.2.0"
"postcss": "^8.5.10",
"ip-address": "10.1.1",
"hono": "^4.12.18",
"@hono/node-server": "^1.19.13",
"react": "$react",
"react-dom": "$react-dom"
} }
} }

View File

@@ -3,13 +3,13 @@
* cursor-tap — capture cursor agent.v1.AgentService/Run wire bytes for tests. * cursor-tap — capture cursor agent.v1.AgentService/Run wire bytes for tests.
* *
* Usage: * Usage:
* CURSOR_TOKEN=... node scripts/cursor-tap.cjs <fixture-name> <prompt> * CURSOR_TOKEN=... node scripts/ad-hoc/cursor-tap.cjs <fixture-name> <prompt>
* *
* Examples: * Examples:
* node scripts/cursor-tap.cjs single-turn-chat "say only PING" * node scripts/ad-hoc/cursor-tap.cjs single-turn-chat "say only PING"
* node scripts/cursor-tap.cjs system-prompt "be brief|hi" # split on first '|' * node scripts/ad-hoc/cursor-tap.cjs system-prompt "be brief|hi" # split on first '|'
* node scripts/cursor-tap.cjs tool-call "weather in Paris" --tools=get_weather * node scripts/ad-hoc/cursor-tap.cjs tool-call "weather in Paris" --tools=get_weather
* node scripts/cursor-tap.cjs composer-2-fast "hi" --model=composer-2-fast * node scripts/ad-hoc/cursor-tap.cjs composer-2-fast "hi" --model=composer-2-fast
* *
* Writes the upstream response bytes to tests/fixtures/cursor/<fixture-name>.bin * Writes the upstream response bytes to tests/fixtures/cursor/<fixture-name>.bin
* and prints decoded summary to stdout. Use these fixtures in unit tests to * and prints decoded summary to stdout. Use these fixtures in unit tests to
@@ -27,7 +27,9 @@ const crypto = require("crypto");
const args = process.argv.slice(2); const args = process.argv.slice(2);
if (args.length < 2) { if (args.length < 2) {
console.error("Usage: cursor-tap.cjs <fixture-name> <prompt> [--model=...] [--tools=name1,name2]"); console.error(
"Usage: cursor-tap.cjs <fixture-name> <prompt> [--model=...] [--tools=name1,name2]"
);
process.exit(1); process.exit(1);
} }

View File

@@ -64,7 +64,7 @@ async function main() {
}; };
if (!(await isServerReady())) { if (!(await isServerReady())) {
serverProcess = spawn(process.execPath, ["scripts/run-next-playwright.mjs", "dev"], { serverProcess = spawn(process.execPath, ["scripts/dev/run-next-playwright.mjs", "dev"], {
stdio: "inherit", stdio: "inherit",
env: testEnv, env: testEnv,
}); });

View File

@@ -24,7 +24,7 @@ const backupDir = resolvePlaywrightAppBackupDir({
appDirExists: existsSync(appDir), appDirExists: existsSync(appDir),
}); });
const usingAlternativeBackupDir = backupDir !== defaultBackupDir; const usingAlternativeBackupDir = backupDir !== defaultBackupDir;
const buildScript = join(cwd, "scripts", "build-next-isolated.mjs"); const buildScript = join(cwd, "scripts", "build", "build-next-isolated.mjs");
const standaloneServer = join(cwd, testDistDir(), "standalone", "server.js"); const standaloneServer = join(cwd, testDistDir(), "standalone", "server.js");
const rootStaticDir = join(cwd, testDistDir(), "static"); const rootStaticDir = join(cwd, testDistDir(), "static");
const rootPublicDir = join(cwd, "public"); const rootPublicDir = join(cwd, "public");

View File

@@ -60,7 +60,7 @@ async function main() {
}; };
if (!(await isServerReady())) { if (!(await isServerReady())) {
serverProcess = spawn(process.execPath, ["scripts/run-next-playwright.mjs", "dev"], { serverProcess = spawn(process.execPath, ["scripts/dev/run-next-playwright.mjs", "dev"], {
stdio: "inherit", stdio: "inherit",
env: testEnv, env: testEnv,
}); });

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
// Generates docs/reference/PROVIDER_REFERENCE.md from src/shared/constants/providers.ts. // Generates docs/reference/PROVIDER_REFERENCE.md from src/shared/constants/providers.ts.
// Run: node --import tsx/esm scripts/docs/gen-provider-reference.ts // Run: node --import tsx scripts/docs/gen-provider-reference.ts
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";

View File

@@ -15,6 +15,7 @@
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import matter from "gray-matter"; import matter from "gray-matter";
import { format } from "prettier";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -36,6 +37,12 @@ const SECTION_ORDER = [
const SECTION_INDEX = Object.fromEntries(SECTION_ORDER.map((s, i) => [s.title, i + 1])); const SECTION_INDEX = Object.fromEntries(SECTION_ORDER.map((s, i) => [s.title, i + 1]));
async function writeGeneratedFile(content) {
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
const formatted = await format(content, { parser: "typescript", printWidth: 100 });
fs.writeFileSync(OUT_FILE, formatted, "utf8");
}
function extractTitleFromContent(content) { function extractTitleFromContent(content) {
const match = content.match(/^#\s+(.+)$/m); const match = content.match(/^#\s+(.+)$/m);
if (match) { if (match) {
@@ -72,10 +79,8 @@ function extractContentPreview(content) {
return stripped.slice(0, 300); return stripped.slice(0, 300);
} }
function emitEmpty(reason) { async function emitEmpty(reason) {
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true }); await writeGeneratedFile(
fs.writeFileSync(
OUT_FILE,
`// AUTO-GENERATED by scripts/docs/generate-docs-index.mjs — DO NOT EDIT MANUALLY `// AUTO-GENERATED by scripts/docs/generate-docs-index.mjs — DO NOT EDIT MANUALLY
// Regenerate with: node scripts/docs/generate-docs-index.mjs // Regenerate with: node scripts/docs/generate-docs-index.mjs
@@ -104,8 +109,7 @@ export const autoNavSections: AutoGenNavSection[] = [];
export const autoSearchIndex: AutoGenSearchItem[] = []; export const autoSearchIndex: AutoGenSearchItem[] = [];
export const autoAllSlugs: string[] = []; export const autoAllSlugs: string[] = [];
`, `
"utf8"
); );
console.warn(`[generate-docs-index] ${reason}; generated empty docs index.`); console.warn(`[generate-docs-index] ${reason}; generated empty docs index.`);
} }
@@ -119,7 +123,7 @@ if (!fs.existsSync(DOCS_DIR)) {
); );
process.exit(0); process.exit(0);
} }
emitEmpty(`${DOCS_DIR} not found`); await emitEmpty(`${DOCS_DIR} not found`);
process.exit(0); process.exit(0);
} }
@@ -168,7 +172,7 @@ for (const { dir, title } of SECTION_ORDER) {
} }
if (docs.length === 0) { if (docs.length === 0) {
emitEmpty("no docs discovered in subfolders"); await emitEmpty("no docs discovered in subfolders");
process.exit(0); process.exit(0);
} }
@@ -258,5 +262,5 @@ export const autoAllSlugs: string[] = ${JSON.stringify(
)}; )};
`; `;
fs.writeFileSync(OUT_FILE, output, "utf8"); await writeGeneratedFile(output);
console.log(`✅ Generated ${OUT_FILE} with ${docs.length} docs, ${navSections.length} sections`); console.log(`✅ Generated ${OUT_FILE} with ${docs.length} docs, ${navSections.length} sections`);

View File

@@ -357,7 +357,7 @@ function ConnectionCooldownCard({
/> />
<BooleanField <BooleanField
label="Use upstream retry hints" label="Use upstream retry hints"
description="Usa valores de retry-after/reset do upstream quando disponíveis." description="Use upstream retry-after/reset values when available."
checked={current.useUpstreamRetryHints} checked={current.useUpstreamRetryHints}
onChange={(useUpstreamRetryHints) => onChange={(useUpstreamRetryHints) =>
setDraft((prev) => ({ setDraft((prev) => ({
@@ -409,7 +409,7 @@ function ConnectionCooldownCard({
</p> </p>
</div> </div>
<NumberField <NumberField
label="Máximo de passos de backoff" label="Max backoff steps"
value={current.maxBackoffSteps} value={current.maxBackoffSteps}
min={0} min={0}
onChange={(maxBackoffSteps) => onChange={(maxBackoffSteps) =>
@@ -424,9 +424,9 @@ function ConnectionCooldownCard({
<span className="font-mono text-text-main">{formatMs(current.baseCooldownMs)}</span> <span className="font-mono text-text-main">{formatMs(current.baseCooldownMs)}</span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Usar dicas de retry do upstream</span> <span className="text-text-muted">Use upstream retry hints</span>
<span className="font-mono text-text-main"> <span className="font-mono text-text-main">
{current.useUpstreamRetryHints ? "Sim" : "Não"} {current.useUpstreamRetryHints ? "Yes" : "No"}
</span> </span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
@@ -440,7 +440,7 @@ function ConnectionCooldownCard({
</span> </span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Máximo de passos de backoff</span> <span className="text-text-muted">Max backoff steps</span>
<span className="font-mono text-text-main">{current.maxBackoffSteps}</span> <span className="font-mono text-text-main">{current.maxBackoffSteps}</span>
</div> </div>
</> </>
@@ -455,12 +455,12 @@ function ConnectionCooldownCard({
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="material-symbols-outlined text-xl text-primary">timer_off</span> <span className="material-symbols-outlined text-xl text-primary">timer_off</span>
<h2 className="text-lg font-bold">Cooldown de Conexão</h2> <h2 className="text-lg font-bold">Connection Cooldown</h2>
</div> </div>
<SectionDescription <SectionDescription
scope="Conexão individual" scope="Individual connection"
trigger="Quando uma conexão retorna falha transitória no upstream" trigger="When a connection returns a transient upstream failure"
effect="Pula temporariamente essa conexão e aumenta backoff em falhas repetidas" effect="Temporarily skips that connection and increases backoff after repeated failures"
/> />
</div> </div>
<ActionRow <ActionRow
@@ -498,13 +498,13 @@ function ConnectionCooldownCard({
</div> </div>
<p className="mb-4 text-sm text-text-muted"> <p className="mb-4 text-sm text-text-muted">
O cooldown base cobre falhas transitórias de conexão. Quando as dicas de retry do upstream Base cooldown covers transient connection failures. When upstream retry hints are enabled,
estão ativas, a janela explícita do provedor sobrescreve o cooldown local. the provider&apos;s explicit retry window overrides the local cooldown.
</p> </p>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2"> <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{renderProfile("oauth", "Provedores OAuth", "lock")} {renderProfile("oauth", "OAuth Providers", "lock")}
{renderProfile("apikey", "Provedores API Key", "key")} {renderProfile("apikey", "API Key Providers", "key")}
</div> </div>
</Card> </Card>
); );

View File

@@ -1,6 +1,13 @@
import path from "node:path"; import path from "node:path";
import os from "node:os"; import os from "node:os";
import { generateClaudeConfig } from "./claude";
import { generateClineConfig } from "./cline";
import { generateCodexConfig } from "./codex";
import { generateContinueConfig } from "./continue";
import { generateKilocodeConfig } from "./kilocode";
import { generateOpencodeConfig } from "./opencode";
export interface GenerateOptions { export interface GenerateOptions {
baseUrl: string; baseUrl: string;
apiKey: string; apiKey: string;
@@ -37,21 +44,16 @@ const TOOL_CONFIG_PATHS: Record<string, string> = {
continue: path.join(os.homedir(), ".continue", "config.yaml"), continue: path.join(os.homedir(), ".continue", "config.yaml"),
}; };
async function importGenerator(toolId: string) { type ConfigGenerator = (options: GenerateOptions) => string | Promise<string>;
const generators: Record<string, { module: string; export: string }> = {
claude: { module: "./claude.js", export: "generateClaudeConfig" },
codex: { module: "./codex.js", export: "generateCodexConfig" },
opencode: { module: "./opencode.js", export: "generateOpencodeConfig" },
cline: { module: "./cline.js", export: "generateClineConfig" },
kilocode: { module: "./kilocode.js", export: "generateKilocodeConfig" },
continue: { module: "./continue.js", export: "generateContinueConfig" },
};
const gen = generators[toolId]; const GENERATORS: Record<string, ConfigGenerator> = {
if (!gen) return null; claude: generateClaudeConfig,
const mod = await import(gen.module); codex: generateCodexConfig,
return { generate: mod[gen.export] }; opencode: generateOpencodeConfig,
} cline: generateClineConfig,
kilocode: generateKilocodeConfig,
continue: generateContinueConfig,
};
export async function generateConfig( export async function generateConfig(
toolId: string, toolId: string,
@@ -70,11 +72,11 @@ export async function generateConfig(
} }
try { try {
const mod = await importGenerator(toolId); const generate = GENERATORS[toolId];
if (!mod) { if (!generate) {
return { success: false, configPath: "", error: `Unknown tool: ${toolId}` }; return { success: false, configPath: "", error: `Unknown tool: ${toolId}` };
} }
const content = await mod.generate(options); const content = await generate(options);
const configPath = TOOL_CONFIG_PATHS[toolId] || ""; const configPath = TOOL_CONFIG_PATHS[toolId] || "";
return { success: true, configPath, content }; return { success: true, configPath, content };
} catch (err) { } catch (err) {

View File

@@ -2,10 +2,28 @@
import React from "react"; import React from "react";
import { act } from "react"; import { act } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const cleanupCallbacks: Array<() => void> = []; const cleanupCallbacks: Array<() => void> = [];
function createTestStorage(): Storage {
const entries = new Map<string, string>();
return {
get length() {
return entries.size;
},
clear: () => entries.clear(),
getItem: (key) => entries.get(key) ?? null,
key: (index) => Array.from(entries.keys())[index] ?? null,
removeItem: (key) => {
entries.delete(key);
},
setItem: (key, value) => {
entries.set(key, value);
},
};
}
function makeContainer(): HTMLElement { function makeContainer(): HTMLElement {
const container = document.createElement("div"); const container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
@@ -20,6 +38,7 @@ describe("AutoRoutingBanner", () => {
( (
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true; ).IS_REACT_ACT_ENVIRONMENT = true;
vi.stubGlobal("localStorage", createTestStorage());
localStorage.clear(); localStorage.clear();
}); });
@@ -29,6 +48,7 @@ describe("AutoRoutingBanner", () => {
} }
document.body.innerHTML = ""; document.body.innerHTML = "";
localStorage.clear(); localStorage.clear();
vi.unstubAllGlobals();
}); });
it("renders banner on first mount", async () => { it("renders banner on first mount", async () => {

View File

@@ -100,7 +100,7 @@ describe("Protocol clients E2E", () => {
async () => { async () => {
const transport = new StdioClientTransport({ const transport = new StdioClientTransport({
command: process.execPath, command: process.execPath,
args: ["--import", "tsx/esm", "open-sse/mcp-server/server.ts"], args: ["--import", "tsx", "open-sse/mcp-server/server.ts"],
env: { env: {
...process.env, ...process.env,
OMNIROUTE_BASE_URL: BASE_URL, OMNIROUTE_BASE_URL: BASE_URL,

View File

@@ -305,13 +305,14 @@ test.describe("Resilience Plan Alignment", () => {
await mockResilienceSettings(page); await mockResilienceSettings(page);
await gotoDashboardRoute(page, "/dashboard/settings?tab=resilience"); await gotoDashboardRoute(page, "/dashboard/settings?tab=resilience");
const resiliencePanel = page.getByRole("tabpanel", { name: "Resilience" });
await expect( await expect(
page.getByRole("heading", { name: "Connection Cooldown", exact: true }) resiliencePanel.getByRole("heading", { name: "Connection Cooldown", exact: true })
).toBeVisible({ timeout: 15000 }); ).toBeVisible({ timeout: 15000 });
await expect(page.getByText("Base cooldown", { exact: true }).first()).toBeVisible(); await expect(resiliencePanel.getByText(/Cooldown base\s*60000ms/)).toBeVisible();
await expect(page.getByText("Use upstream retry hints", { exact: true }).first()).toBeVisible(); await expect(resiliencePanel.getByText(/Use upstream retry hints\s*No/)).toBeVisible();
await expect(page.getByText("Max backoff steps", { exact: true }).first()).toBeVisible(); await expect(resiliencePanel.getByText(/Max backoff steps\s*8/)).toBeVisible();
await expect(page.getByText(/Rate-limit fallback/i)).toHaveCount(0); await expect(resiliencePanel.getByText(/Rate-limit fallback/i)).toHaveCount(0);
}); });
test("health page renders provider breaker runtime state for multiple providers", async ({ test("health page renders provider breaker runtime state for multiple providers", async ({

View File

@@ -13,7 +13,7 @@
* *
* To run: * To run:
* CURSOR_E2E_TOKEN=$(cat ~/.cursor/access-token) \ * CURSOR_E2E_TOKEN=$(cat ~/.cursor/access-token) \
* node --import tsx/esm --test tests/integration/cursor-e2e.test.ts * node --import tsx --test tests/integration/cursor-e2e.test.ts
* *
* Capturing wire fixtures (separate workflow): * Capturing wire fixtures (separate workflow):
* CURSOR_TOKEN=... node scripts/ad-hoc/cursor-tap.cjs single-turn-chat "say PING" * CURSOR_TOKEN=... node scripts/ad-hoc/cursor-tap.cjs single-turn-chat "say PING"

View File

@@ -4,7 +4,7 @@
* Tests bulk data operations against acceptable time thresholds. * Tests bulk data operations against acceptable time thresholds.
* Thresholds are 2x the expected target to account for slow CI machines. * Thresholds are 2x the expected target to account for slow CI machines.
* *
* Run: node --import tsx/esm --test tests/integration/performance-regression.test.ts * Run: node --import tsx --test tests/integration/performance-regression.test.ts
*/ */
import { describe, it, before, after } from "node:test"; import { describe, it, before, after } from "node:test";

View File

@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
import { createChatPipelineHarness } from "../integration/_chatPipelineHarness.ts"; import { createChatPipelineHarness } from "../integration/_chatPipelineHarness.ts";
process.env.STREAM_IDLE_TIMEOUT_MS = "50"; process.env.STREAM_IDLE_TIMEOUT_MS = "50";
process.env.STREAM_READINESS_TIMEOUT_MS = "50";
const harness = await createChatPipelineHarness("chat-cooldown-aware-retry"); const harness = await createChatPipelineHarness("chat-cooldown-aware-retry");
const auth = await import("../../src/sse/services/auth.ts"); const auth = await import("../../src/sse/services/auth.ts");
@@ -241,53 +242,41 @@ test("handleChat returns model_cooldown when every credential for the requested
assert.ok(Number(response.headers.get("Retry-After")) >= 1); assert.ok(Number(response.headers.get("Retry-After")) >= 1);
}); });
test("handleChat returns stream readiness timeout without entering cooldown-aware retry or account lockout", async (t) => { test("handleChat returns stream readiness timeout without entering cooldown-aware retry or account lockout", async () => {
t.mock.timers.enable({ apis: ["setTimeout"] }); const connection = await seedConnection("openai", {
try { apiKey: "sk-openai-stream-readiness-timeout",
const connection = await seedConnection("openai", { });
apiKey: "sk-openai-stream-readiness-timeout", await settingsDb.updateSettings({
}); requestRetry: 1,
await settingsDb.updateSettings({ maxRetryIntervalSec: 10,
requestRetry: 1, });
maxRetryIntervalSec: 10,
});
let fetchCalls = 0; let fetchCalls = 0;
globalThis.fetch = async () => { globalThis.fetch = async () => {
fetchCalls += 1; fetchCalls += 1;
return buildZombieSseResponse(); return buildZombieSseResponse();
}; };
const responsePromise = handleChat( const response = await handleChat(
buildRequest({ buildRequest({
body: { body: {
model: "openai/gpt-4o-mini", model: "openai/gpt-4o-mini",
stream: true, stream: true,
messages: [{ role: "user", content: "trigger zombie stream" }], messages: [{ role: "user", content: "trigger zombie stream" }],
}, },
}) })
); );
const body = (await response.json()) as any;
// Give the async pipeline a chance to settle and start the fetch assert.equal(response.status, 504);
await new Promise((resolve) => process.nextTick(resolve)); assert.equal(fetchCalls, 1);
// Fast-forward past the default 30s stream readiness timeout assert.equal(body.error.code, "STREAM_READINESS_TIMEOUT");
t.mock.timers.tick(30500);
const response = await responsePromise; const refreshedConnection = (await getProviderConnectionById((connection as any).id)) as any;
const body = (await response.json()) as any; assert.equal(refreshedConnection.testStatus, "active");
assert.ok(refreshedConnection.rateLimitedUntil == null);
assert.equal(response.status, 504); assert.ok(refreshedConnection.errorCode == null);
assert.equal(fetchCalls, 1); assert.equal(refreshedConnection.backoffLevel, 0);
assert.equal(body.error.code, "STREAM_READINESS_TIMEOUT");
const refreshedConnection = (await getProviderConnectionById((connection as any).id)) as any;
assert.equal(refreshedConnection.testStatus, "active");
assert.ok(refreshedConnection.rateLimitedUntil == null);
assert.ok(refreshedConnection.errorCode == null);
assert.equal(refreshedConnection.backoffLevel, 0);
} finally {
t.mock.timers.reset();
}
}); });
test("handleChat aborts the pending cooldown wait when the client disconnects", async () => { test("handleChat aborts the pending cooldown wait when the client disconnects", async () => {

View File

@@ -140,13 +140,16 @@ test("Command Code executor posts wrapped body and required headers to alpha/gen
}); });
test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => { test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => {
globalThis.fetch = async () => const calls: any[] = [];
commandCodeStream([ globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([
{ type: "text-delta", text: "Hello" }, { type: "text-delta", text: "Hello" },
{ type: "reasoning-delta", text: "thinking" }, { type: "reasoning-delta", text: "thinking" },
{ type: "tool-call", toolCallId: "call_1", toolName: "search", input: { q: "docs" } }, { type: "tool-call", toolCallId: "call_1", toolName: "search", input: { q: "docs" } },
{ type: "finish", finishReason: "tool-calls" }, { type: "finish", finishReason: "tool-calls" },
]); ]);
};
const { response } = await getExecutor("command-code").execute({ const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4", model: "gpt-5.4",
@@ -155,6 +158,7 @@ test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () =
body: { messages: [{ role: "user", content: "Hi" }] }, body: { messages: [{ role: "user", content: "Hi" }] },
}); });
assert.equal(calls[0].body.params.stream, true);
assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8"); assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8");
const sse = await response.text(); const sse = await response.text();
assert.match(sse, /data: \[DONE\]/); assert.match(sse, /data: \[DONE\]/);

View File

@@ -85,6 +85,9 @@ test("machineId: falls back to Linux machine-id files before hostname", async ()
fs.existsSync = () => false; fs.existsSync = () => false;
fs.readFileSync = (filePath, encoding) => { fs.readFileSync = (filePath, encoding) => {
if (filePath !== "/etc/machine-id") {
return originalReadFileSync(filePath, encoding);
}
assert.equal(filePath, "/etc/machine-id"); assert.equal(filePath, "/etc/machine-id");
assert.equal(encoding, "utf8"); assert.equal(encoding, "utf8");
return "LINUX-MACHINE-ID\n"; return "LINUX-MACHINE-ID\n";

View File

@@ -2,10 +2,28 @@
import React from "react"; import React from "react";
import { act } from "react"; import { act } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const cleanupCallbacks: Array<() => void> = []; const cleanupCallbacks: Array<() => void> = [];
function createTestStorage(): Storage {
const entries = new Map<string, string>();
return {
get length() {
return entries.size;
},
clear: () => entries.clear(),
getItem: (key) => entries.get(key) ?? null,
key: (index) => Array.from(entries.keys())[index] ?? null,
removeItem: (key) => {
entries.delete(key);
},
setItem: (key, value) => {
entries.set(key, value);
},
};
}
function makeContainer(): HTMLElement { function makeContainer(): HTMLElement {
const container = document.createElement("div"); const container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
@@ -20,6 +38,7 @@ describe("AutoRoutingBanner", () => {
( (
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true; ).IS_REACT_ACT_ENVIRONMENT = true;
vi.stubGlobal("localStorage", createTestStorage());
localStorage.clear(); localStorage.clear();
}); });
@@ -29,6 +48,7 @@ describe("AutoRoutingBanner", () => {
} }
document.body.innerHTML = ""; document.body.innerHTML = "";
localStorage.clear(); localStorage.clear();
vi.unstubAllGlobals();
}); });
it("renders banner on first mount", async () => { it("renders banner on first mount", async () => {

View File

@@ -10,6 +10,23 @@ function decodeChunk(value) {
return typeof value === "string" ? value : new TextDecoder().decode(value); return typeof value === "string" ? value : new TextDecoder().decode(value);
} }
async function readWithTimeout(reader, timeoutMs = 250) {
let timeout;
try {
return await Promise.race([
reader.read(),
new Promise((_, reject) => {
timeout = setTimeout(
() => reject(new Error("Timed out waiting for SSE heartbeat")),
timeoutMs
);
}),
]);
} finally {
clearTimeout(timeout);
}
}
test("integration: anthropic-ping heartbeat reaches downstream and does NOT trigger stream.ts strip", async () => { test("integration: anthropic-ping heartbeat reaches downstream and does NOT trigger stream.ts strip", async () => {
// Build a fake upstream that emits one chunk then idles indefinitely // Build a fake upstream that emits one chunk then idles indefinitely
let cancelled = false; let cancelled = false;
@@ -39,7 +56,7 @@ test("integration: anthropic-ping heartbeat reaches downstream and does NOT trig
const startedAt = Date.now(); const startedAt = Date.now();
let sawPing = false; let sawPing = false;
while (Date.now() - startedAt < 200) { while (Date.now() - startedAt < 200) {
const { value, done } = await reader.read(); const { value, done } = await readWithTimeout(reader);
if (done) break; if (done) break;
const chunk = decodeChunk(value); const chunk = decodeChunk(value);
if (/^event: ping\b/m.test(chunk)) { if (/^event: ping\b/m.test(chunk)) {
@@ -83,7 +100,7 @@ test("integration: openai-chunk heartbeat is valid JSON parseable by SDKs", asyn
const startedAt = Date.now(); const startedAt = Date.now();
let sawValidChunk = false; let sawValidChunk = false;
while (Date.now() - startedAt < 200) { while (Date.now() - startedAt < 200) {
const { value, done } = await reader.read(); const { value, done } = await readWithTimeout(reader);
if (done) break; if (done) break;
const chunk = decodeChunk(value); const chunk = decodeChunk(value);
if (chunk.startsWith("data: ") && chunk.includes("omniroute-keepalive")) { if (chunk.startsWith("data: ") && chunk.includes("omniroute-keepalive")) {
@@ -118,7 +135,7 @@ test("integration: shapeForClientFormat + createSseHeartbeatTransform pipeline (
const reader = upstream.pipeThrough(transform).getReader(); const reader = upstream.pipeThrough(transform).getReader();
await reader.read(); // first real await reader.read(); // first real
const { value } = await reader.read(); const { value } = await readWithTimeout(reader);
assert.match(decodeChunk(value), /^event: ping\ndata: \{\}\n\n$/); assert.match(decodeChunk(value), /^event: ping\ndata: \{\}\n\n$/);
await reader.cancel(); await reader.cancel();
}); });