diff --git a/.env.example b/.env.example index 60b5074e6e..4a0198dc28 100644 --- a/.env.example +++ b/.env.example @@ -505,7 +505,9 @@ NEXT_PUBLIC_CLOUD_URL= #OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/ #OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com #OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/quota -#OMNIROUTE_OPENCODE_GO_QUOTA_URL=https://api.z.ai/api/monitor/usage/quota/limit +# OpenCode Go has no public quota API — this has no default and stays +# unset unless you explicitly opt in to a self-hosted/mirrored endpoint: +#OMNIROUTE_OPENCODE_GO_QUOTA_URL= #OMNIROUTE_OPENCODE_GO_DASHBOARD_URL=https://opencode.ai/workspace #OMNIROUTE_OLLAMA_CLOUD_USAGE_URL=https://ollama.com/settings diff --git a/.env.homolog.example b/.env.homolog.example new file mode 100644 index 0000000000..03920a7707 --- /dev/null +++ b/.env.homolog.example @@ -0,0 +1,9 @@ +# Homologação E2E real — copie para .env.homolog (NUNCA commitar o real) +HOMOLOG_BASE_URL=http://192.168.0.15:20128 +# Senha de management do dashboard da VPS (a mesma do /login) +HOMOLOG_ADMIN_PASSWORD= +# Deixe vazio: a suíte cria uma API key efêmera via admin e revoga no fim. +# Só preencha para depurar uma camada isolada com uma key fixa. +HOMOLOG_API_KEY= +# Tier crítico (chat real, max_tokens=5). Demais providers: só validação de catálogo. +HOMOLOG_CRITICAL_PROVIDERS=openai,anthropic,gemini,codex,grok,glm,deepseek,openrouter diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3c641330f1..0913d78cd0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,6 +24,15 @@ updates: update-types: ["version-update:semver-major"] - dependency-name: "eslint-config-next" update-types: ["version-update:semver-major"] + # typescript majors are peer-blocked by typescript-eslint, which pins a hard + # upper bound (8.64.0 → peerDependencies.typescript ">=4.8.4 <6.1.0"). A TS 7 + # bump therefore violates the peer and takes down the whole toolchain at once — + # #7068 grouped it with 6 harmless bumps and turned Build + Lint + Quality Ratchet + # + Unit (6/8, 8/8) + Integration (1/2, 2/2) + dast-smoke red in one shot, blocking + # the innocuous updates riding along with it. Un-ignore once typescript-eslint + # widens the peer, and migrate TS majors intentionally (own PR, own CI run). + - dependency-name: "typescript" + update-types: ["version-update:semver-major"] # jscpd v5 is a Rust rewrite (native binary, no Node.js programmatic API). # scripts/check/check-duplication.mjs is deliberately pinned to jscpd@4 (it # parses jscpd-report.json against a frozen baseline). A v5 major would break diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb642d7863..917ca860aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: docs: ${{ steps.classify.outputs.docs }} i18n: ${{ steps.classify.outputs.i18n }} workflow: ${{ steps.classify.outputs.workflow }} + testsOnly: ${{ steps.classify.outputs.testsOnly }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with: @@ -124,11 +125,23 @@ jobs: - run: npm run check:route-guard-membership - run: npm run check:test-discovery - run: npm run check:tracked-artifacts + # WS1.7 (v3.8.49 plan): Dockerfile lint (hadolint, pinned by digest). + # failure-threshold=error keeps the 5 pre-existing warnings (DL3008/DL3003/ + # DL3016 version pinning / WORKDIR) visible without blocking; any ERROR fails. + - name: hadolint (Dockerfile) + run: docker run --rm -i hadolint/hadolint@sha256:27086352fd5e1907ea2b934eb1023f217c5ae087992eb59fde121dce9c9ff21e hadolint --failure-threshold error - < Dockerfile - run: npm run check:lockfile - run: npm run check:licenses # check:docs-sync is run by the docs-sync-strict job (via check:docs-all) and the # husky pre-commit hook; the standalone copy here was redundant (ROI dedup). - run: npm run typecheck:core + # #7033: typecheck:core's curated file allowlist does not cover + # src/app/(dashboard) TSX (and next.config.mjs sets ignoreBuildErrors: + # true, so `next build` never type-checks it either) — orphaned + # identifiers there (see #6625/#6909) were invisible to CI. This gate + # runs tsc scoped to the dashboard tree against a frozen baseline of + # pre-existing errors; only NEW errors fail it. + - run: npm run check:dashboard-typecheck # typecheck:noimplicit:core dropped from this job (2026-07 optimize): # it was advisory (continue-on-error) and largely subsumed by the blocking # check:type-coverage ratchet in quality-gate. Local: npm run typecheck:noimplicit:core. @@ -148,7 +161,7 @@ jobs: # The coverage.* metrics degrade gracefully: the download is continue-on-error and # the ratchet runs with --allow-missing, so absent coverage is skipped, not failed. # Path filter: code-only — pure docs/i18n PRs have nothing for these ratchets to guard. - if: ${{ !cancelled() && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true' && (needs.lint.result == 'success' || needs.lint.result == 'failure'))) }} + if: ${{ !cancelled() && !contains(github.event.pull_request.labels.*.name, 'hotfix') && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true' && (needs.lint.result == 'success' || needs.lint.result == 'failure'))) }} # security-events: read lets the CodeQL ratchet read open code-scanning alerts # via `gh api .../code-scanning/alerts`. contents: read keeps checkout working. permissions: @@ -258,7 +271,7 @@ jobs: # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). # Path filter: code-only (scanners/ratchets target production surface). - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true') }} + if: ${{ !contains(github.event.pull_request.labels.*.name, 'hotfix') && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true')) }} steps: # fetch-depth: 0 — the OpenAPI breaking-change gate (oasdiff) reads the base # spec via `git show :docs/openapi.yaml`; a shallow clone @@ -609,12 +622,26 @@ jobs: - name: Assert dist/server.js exists run: test -f dist/server.js || (echo "dist/server.js missing — build:cli did not assemble correctly" && exit 1) - run: npm run check:pack-artifact + # WS1.2 (#7065 class): pack the real tarball, install it into a clean prefix and + # BOOT it to a healthy /api/monitoring/health — the gate that structure checks + # cannot provide (3 releases shipped boot-crashing tarballs with green lists). + - name: Boot-smoke the packed tarball + run: npm run check:pack-boot electron-package-smoke: - name: Electron Package Smoke - runs-on: ubuntu-latest - timeout-minutes: 25 + name: Electron Package Smoke (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 needs: build + # WS1.5 (v3.8.49 plan): the Electron rebuild/spawn path previously executed for + # the FIRST time on the release tag — the v3.8.48 Windows bug (npx.cmd spawned + # without shell, CVE-2024-27980 behavior change) could only surface at release. + # windows-latest runs prepare:bundle (the ABI rebuild + spawn plan) per release + # PR; ubuntu keeps the full pack + headless smoke. + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation CSC_IDENTITY_AUTO_DISCOVERY: "false" @@ -640,9 +667,21 @@ jobs: working-directory: electron run: npm install --no-audit --no-fund - name: Pack Electron app + if: runner.os == 'Linux' working-directory: electron run: npm run pack + # ADVISORY while the new Windows leg matures (repo convention, dast-smoke + # precedent): its first-ever real run (2026-07-15, run 29457533565) died in + # 0.7s with the error swallowed by pwsh — bash shell captures stderr and + # continue-on-error keeps the heavy gate green while we harden it (#7336). + - name: Prepare Electron standalone (Windows ABI rebuild + spawn path) + if: runner.os == 'Windows' + working-directory: electron + continue-on-error: true + shell: bash + run: npm run prepare:bundle 2>&1 - name: Smoke packaged Electron app + if: runner.os == 'Linux' env: ELECTRON_SMOKE_TIMEOUT_MS: 60000 run: xvfb-run -a npm run electron:smoke:packaged @@ -725,12 +764,23 @@ jobs: - uses: ./.github/actions/npm-ci-retry # The second test runner (CLAUDE.md: "Both test runners must pass") — was never # wired into CI until the 2026-06-09 quality audit (Fase 6A.2). - - run: npm run test:vitest - # vitest:ui is RED today (14 fails — UI component drift accumulated while the - # suite never ran in CI). Informational until the Fase 6A triage (2026-06-16+) - # fixes the components/tests; then drop continue-on-error to make it blocking. - - run: npm run test:vitest:ui + # WS5.2/5.3 (v3.8.49 plan): JUnit output feeds Trunk Flaky Tests (advisory upload + # below). node:test stays OUT of the first wave (fd1-sensitive reporter stream). + - run: npm run test:vitest -- --reporter=default --reporter=junit --outputFile.junit=trunk-junit/vitest-mcp.xml + # vitest:ui went back to 870/870 green in the v3.8.49 quality plan (WS6.1, + # PR #7127 — 69 fails triaged: matchMedia polyfill, node:test→vitest migration, + # CompareTab D22 cap). Promoted to BLOCKING per the plan's post-merge step. + - run: npm run test:vitest:ui -- --reporter=default --reporter=junit --outputFile.junit=trunk-junit/vitest-ui.xml + # Trunk Flaky Tests upload — advisory (never blocks), own-origin only (fork PRs + # have no TRUNK_TOKEN). Pinned by SHA (tag v2.1.2). + - name: Upload test results to Trunk (advisory) + if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} continue-on-error: true + uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 + with: + junit-paths: trunk-junit/**/*.xml + org-slug: omniroute + token: ${{ secrets.TRUNK_TOKEN }} # Node 24/26 compatibility matrices moved to .github/workflows/nightly-compat.yml # (plano mestre testes+CI, Eixo D2 — they cost ~28% of every heavy run to catch a @@ -739,9 +789,12 @@ jobs: test-coverage: name: Coverage runs-on: ubuntu-latest - timeout-minutes: 10 + # 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it); + # merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive + # release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16). + timeout-minutes: 20 needs: test-unit - if: ${{ !cancelled() && needs.test-unit.result == 'success' }} + if: ${{ !cancelled() && needs.test-unit.result == 'success' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }} env: JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-test-api-key-secret-long @@ -789,6 +842,7 @@ jobs: --merge-async \ --reporter=text-summary \ --reporter=json-summary \ + --reporter=lcov \ --exclude=tests/** \ --exclude=**/*.test.* \ --check-coverage \ @@ -810,6 +864,18 @@ jobs: > coverage/coverage-report.md fi cat coverage/coverage-report.md >> "$GITHUB_STEP_SUMMARY" + # WS5.6 (D7, v3.8.49 plan): patch coverage on the PR diff via Codecov — + # informational during calibration (codecov.yml sets informational: true); + # promote to blocking only after ~2 weeks without false blocks. The lcov + # reporter above also fixes coverage/lcov.info being silently absent + # (if-no-files-found: warn) — Sonar consumes the same file. + - name: Upload coverage to Codecov (informational) + if: always() + uses: codecov/codecov-action@04b047e8bb82a0c002c8312c1c880fbc6a999d45 # v5 + with: + files: coverage/lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false - name: Upload coverage artifacts if: always() uses: actions/upload-artifact@v7 @@ -960,7 +1026,12 @@ jobs: # ~33%. Playwright browser is cached across runs (~1.5min saved per shard). # Heavy shard target: ≤20min (was ~40min). Timeout 45min to cover slow runners. timeout-minutes: 45 - needs: build + needs: [build, changes] + # WS3.1 hotfix fast-lane: the 9-shard E2E matrix is the CI critical path (~25min). + # It skips for (a) PRs labeled `hotfix` (entry policy in docs/ops/RELEASE_CHECKLIST.md: + # production-broken only, full-suite evidence from the previous green run linked in the + # PR) and (b) tests-only diffs outside tests/e2e/ (cannot change the served app). + if: ${{ needs.changes.outputs.testsOnly != 'true' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }} strategy: fail-fast: false matrix: @@ -995,7 +1066,33 @@ jobs: - name: Extract Next.js build artifact run: | tar -xzf /tmp/e2e-build.tar.gz - - run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/9 + # WS4.1: duration-balanced shards (LPT over config/quality/e2e-timings.json). + # Measured skew of plain --shard was 14× (24m47s vs 1m47s) — E2E was the CI + # critical path. The balancer self-verifies completeness and exits non-zero on + # any inconsistency, falling back to plain --shard (never fewer specs). + - name: Run E2E tests (duration-balanced shard) + env: + SHARD: ${{ matrix.shard }} + PLAYWRIGHT_JUNIT_OUTPUT_NAME: junit-e2e-results.xml + run: | + if FILES=$(node scripts/quality/balance-e2e-shards.mjs "$SHARD" 9); then + if [ -z "$FILES" ]; then echo "[e2e-balance] shard $SHARD has no files"; exit 0; fi + echo "[e2e-balance] shard $SHARD runs:"; echo "$FILES" + # shellcheck disable=SC2086 — FILES is our own newline-separated path list + npx playwright test $(echo "$FILES" | tr '\n' ' ') --reporter=line,junit + else + echo "[e2e-balance] balancer unavailable — plain --shard fallback" + npx playwright test tests/e2e/*.spec.ts --shard="$SHARD"/9 --reporter=line,junit + fi + # WS5.2/5.3: Trunk Flaky Tests upload — advisory, own-origin only, SHA-pinned. + - name: Upload test results to Trunk (advisory) + if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + continue-on-error: true + uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 + with: + junit-paths: junit-e2e-results.xml + org-slug: omniroute + token: ${{ secrets.TRUNK_TOKEN }} test-integration: name: Integration Tests (${{ matrix.shard }}/2) diff --git a/.github/workflows/dast-smoke.yml b/.github/workflows/dast-smoke.yml index 5d8676cea7..e1b5c757e5 100644 --- a/.github/workflows/dast-smoke.yml +++ b/.github/workflows/dast-smoke.yml @@ -10,7 +10,10 @@ jobs: # ADVISORY while this new gate matures (repo convention: advisory -> blocking). # Flip to blocking (remove continue-on-error) once it's proven stable across a few PRs. continue-on-error: true - timeout-minutes: 12 + # Build CLI bundle alone varies 6-11min on GitHub-hosted runners (3 consecutive + # timeouts observed on 2026-07-14 with the old 12min cap killing schemathesis + # mid-run) — 25min leaves real headroom for the actual DAST steps. + timeout-minutes: 25 env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-api-key-secret-with-sufficient-length-aaaa diff --git a/.github/workflows/nightly-release-green.yml b/.github/workflows/nightly-release-green.yml index 0ba8e9301d..63d1b4a054 100644 --- a/.github/workflows/nightly-release-green.yml +++ b/.github/workflows/nightly-release-green.yml @@ -1,11 +1,18 @@ -name: Nightly Release-Green +name: Release-Green (continuous) # Solution D — continuous, NON-BLOCKING drift signal for the active release branch. # # WHY: the full gate (ci.yml) only runs on the release PR (PR → main), so reds # accrue silently on release/** and explode — in layers — at release time. This -# nightly reproduces the release-equivalent validation on the active release branch -# HEAD and, when there are HARD failures, opens/updates a single tracking issue. +# workflow reproduces the release-equivalent validation on the release branch and, +# when there are HARD failures, opens/updates a single tracking issue. +# +# WS5.1 (v3.8.49 quality plan) — two modes: +# push to release/v* (code paths) → --quick (fast HARD gates, ~5-8min). Catches the +# captain's direct pushes (sync-back — the one ungated write path) AND the merged +# COMBINATION right after every PR merge, attributing the offending push range in +# the issue. Base-red MTTD drops from ≤24h to ≤~15min after the offending push. +# schedule (3×/day) → full --with-build --full-ci (the deep sweep incl. build+suites). # # It is NOT a required status check and never touches a contributor PR — it only # reports. Ratchet drift (eslint warnings / cognitive-complexity / file-size) is @@ -14,8 +21,23 @@ name: Nightly Release-Green # package-artifact) flip the issue open. on: + push: + branches: ["release/v*", "main"] + paths: + - "src/**" + - "open-sse/**" + - "bin/**" + - "electron/**" + - "scripts/**" + - "tests/**" + - "config/**" + - "package.json" + - "package-lock.json" + - "tsconfig*.json" schedule: - - cron: "23 5 * * *" # 05:23 UTC daily — off-peak, distinct from other nightlies + - cron: "23 5 * * *" # full sweep — off-peak, distinct from other nightlies + - cron: "23 12 * * *" # full sweep — midday (WS5.1: 3×/day instead of 1×) + - cron: "23 18 * * *" # full sweep — evening workflow_dispatch: inputs: branch: @@ -28,7 +50,9 @@ permissions: issues: write concurrency: - group: nightly-release-green + # push storms during merge campaigns collapse to the newest commit per branch; + # scheduled full sweeps keep their own single lane. + group: release-green-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true env: @@ -37,6 +61,9 @@ env: jobs: release-green: name: Validate active release branch + # On a push, only run for release/* pushes — a push to main is handled by the + # main-green job below. Schedule/dispatch always run (they validate the highest release). + if: ${{ github.event_name != 'push' || startsWith(github.ref_name, 'release/') }} # Dynamic runner: with USE_VPS_RUNNER=true (release window / on-demand pre-flight) # this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY, # no local noauth CLIs => zero machine-specific false positives) and no contention. @@ -56,10 +83,15 @@ jobs: id: branch env: INPUT_BRANCH: ${{ github.event.inputs.branch }} + EVENT_NAME: ${{ github.event_name }} + PUSHED_REF: ${{ github.ref_name }} run: | set -euo pipefail if [ -n "${INPUT_BRANCH:-}" ]; then TARGET="$INPUT_BRANCH" + elif [ "$EVENT_NAME" = "push" ]; then + # validate exactly what was pushed, not the highest branch + TARGET="$PUSHED_REF" else # highest release/vX.Y.Z by semver among remote branches TARGET=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/release/v*' \ @@ -93,16 +125,26 @@ jobs: - name: Release-green validation (full) id: validate + env: + EVENT_NAME: ${{ github.event_name }} run: | set +e # --hermetic: scrub live-test trigger vars (self-hosted runner may carry # operator env; hosted ignores the unknown flag before #6300 lands). - # --full-ci: ALSO run every static gate from ci.yml's gate jobs (lint, - # quality-gate, quality-extended, docs-sync-strict, pr-test-policy). PRs into - # release/** only get the fast-gates, so these accrue silently and explode in - # layers on the release PR (v3.8.46: 11 static base-reds leaked). Running them - # nightly opens the tracking issue the moment one lands, not at release time. - node scripts/quality/validate-release-green.mjs --json --with-build --hermetic --full-ci \ + # push → --quick: fast HARD gates only (~5-8min), per-merge signal. + # schedule/dispatch → --with-build --full-ci: ALSO run every static gate from + # ci.yml's gate jobs (lint, quality-gate, quality-extended, docs-sync-strict, + # pr-test-policy) + build + full suites. PRs into release/** only get the + # fast-gates, so these accrue silently and explode in layers on the release PR + # (v3.8.46: 11 static base-reds leaked). + if [ "$EVENT_NAME" = "push" ]; then + MODE="--quick" + else + MODE="--with-build --full-ci" + fi + echo "[release-green] mode: $MODE (event: $EVENT_NAME)" + # shellcheck disable=SC2086 — MODE is an intentional flag list + node scripts/quality/validate-release-green.mjs --json --hermetic $MODE \ 1> release-green.json 2> release-green.log echo "exit=$?" >> "$GITHUB_OUTPUT" echo "------- report -------" @@ -114,15 +156,28 @@ jobs: GH_TOKEN: ${{ github.token }} TARGET: ${{ steps.branch.outputs.target }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before }} + AFTER_SHA: ${{ github.event.after }} run: | set -euo pipefail TITLE="🔴 Release branch not green: ${TARGET}" { - echo "The nightly **release-green** validation found HARD failures on \`${TARGET}\`." + echo "The **release-green** validation found HARD failures on \`${TARGET}\`." echo "These are real defects that would block the release PR — fix them in the" echo "originating PR branch (via co-authorship), not by demanding it from contributors." echo "" - echo "**Run:** ${RUN_URL}" + echo "**Run:** ${RUN_URL} (mode: ${EVENT_NAME})" + # WS5.1 attribution: on push events the offending change IS this push's range + # (one merge per push in the normal queue), so name it — no bisect needed. + if [ "$EVENT_NAME" = "push" ] && [ -n "${BEFORE_SHA:-}" ] && \ + git cat-file -e "$BEFORE_SHA" 2>/dev/null; then + echo "" + echo "**Offending push range** (\`${BEFORE_SHA:0:9}..${AFTER_SHA:0:9}\`):" + echo '```' + git log --no-decorate --oneline "${BEFORE_SHA}..${AFTER_SHA}" | head -20 + echo '```' + fi echo "" echo '```' sed -n '/──────── verdict ────────/,$p' release-green.log || tail -40 release-green.log @@ -149,3 +204,100 @@ jobs: release-green.json release-green.log if-no-files-found: ignore + + # Companion arm for `main`. Under the parallel-cycle model, main only receives merged + # work at the release squash — so a gate/infra fix that lands only on release leaves + # main red the whole cycle, and repo-wide gates (CodeQL alert count, ratchet baselines) + # turn EVERY PR into main red on a check unrelated to its diff. This detects that and + # opens a "🔴 main not green" tracking issue. The PREVENTION is the companion-PR reflex + # (Hard Rule #21 area / _shared/merge-gates.md §8); this is the automated backstop. + main-green: + name: Validate main branch + # On a push, only run for a push to main — a push to release/* is handled by + # release-green above. Schedule/dispatch always run (they also sweep main). + if: ${{ github.event_name != 'push' || github.ref_name == 'main' }} + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }} + env: + JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation + API_KEY_SECRET: ci-nightly-api-key-secret-long + DISABLE_SQLITE_AUTO_BACKUP: "true" + steps: + - uses: actions/checkout@v7 + with: + ref: main # literal — no injection surface; scheduled runs default to the repo default branch (a release/v*), so pin main explicitly + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-node@v6 + with: + node-version: "24" + cache: npm + + - uses: ./.github/actions/npm-ci-retry + + - name: Main-green validation + id: validate + env: + EVENT_NAME: ${{ github.event_name }} + run: | + set +e + # push (a merge into main) → --quick fast HARD gates; schedule/dispatch → full sweep. + if [ "$EVENT_NAME" = "push" ]; then + MODE="--quick" + else + MODE="--with-build --full-ci" + fi + echo "[main-green] mode: $MODE (event: $EVENT_NAME)" + # shellcheck disable=SC2086 — MODE is an intentional flag list + node scripts/quality/validate-release-green.mjs --json --hermetic $MODE \ + 1> main-green.json 2> main-green.log + echo "exit=$?" >> "$GITHUB_OUTPUT" + echo "------- report -------" + cat main-green.log + + - name: Open / update tracking issue on HARD failure + if: steps.validate.outputs.exit != '0' + env: + GH_TOKEN: ${{ github.token }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + EVENT_NAME: ${{ github.event_name }} + run: | + set -euo pipefail + TITLE="🔴 main branch not green" + { + echo "The **main-green** validation found HARD failures on \`main\`." + echo "" + echo "Because \`main\` only receives merged work at the release squash, a gate/infra" + echo "fix that landed only on the release branch leaves \`main\` broken for the whole" + echo "cycle — and repo-wide gates (CodeQL alert count, ratchet baselines) then turn" + echo "**every open PR into main** red on a check unrelated to its diff. The fix is a" + echo "companion PR \`--base main\` carrying the release-side fix (see" + echo "\`_shared/merge-gates.md\` §8), NOT chasing each contributor PR." + echo "" + echo "**Run:** ${RUN_URL} (mode: ${EVENT_NAME})" + echo "" + echo '```' + sed -n '/──────── verdict ────────/,$p' main-green.log || tail -40 main-green.log + echo '```' + echo "" + echo "_Ratchet drift (eslint warnings / cognitive-complexity / file-size) is expected mid-cycle and did NOT, on its own, open this issue._" + } > issue-body.md + + EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \ + --search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "") + if [ -n "$EXISTING" ]; then + gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file issue-body.md + echo "Updated existing issue #$EXISTING" + else + gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body-file issue-body.md + fi + + - name: Upload report artifact + if: always() + uses: actions/upload-artifact@v7 + with: + name: main-green-report + path: | + main-green.json + main-green.log + if-no-files-found: ignore diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 8e74b96d46..1edd3c09e0 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -22,6 +22,14 @@ on: - latest - next - historic + publish_mode: + description: "staged = npm stage publish (owner approves with 2FA after the staged boot-verify); direct = legacy immediate publish (emergency fallback only)" + required: false + default: "staged" + type: choice + options: + - staged + - direct workflow_call: inputs: version: @@ -166,8 +174,34 @@ jobs: TAG: ${{ github.ref_name }} run: gh release upload "$TAG" sbom-npm.cdx.json --clobber - - name: Publish to npm + # WS1.2/WS1.3 (#7065 class): the artifact that is about to be published must + # BOOT. build:cli already assembled dist/ above; this packs+installs+boots the + # real tarball and fails the publish before anything reaches the registry. + - name: Boot-smoke the tarball before ANY publish if: steps.resolve.outputs.skip != 'true' + run: npm run check:pack-boot + + # WS1.3 (D2, v3.8.49 plan): STAGED publishing by default — `npm stage publish` + # parks the exact bytes on the registry WITHOUT making them installable; the + # owner then verifies and approves with 2FA (`npm stage approve`), moving the + # human gate to AFTER the proof instead of before it. Requires npm >= 11.15 + # (staged publishing GA 2026-05-22). publish_mode=direct is the emergency + # fallback (legacy immediate publish) via workflow_dispatch. + - name: Ensure npm supports staged publishing + if: steps.resolve.outputs.skip != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_mode != 'direct') + run: | + set -euo pipefail + CUR=$(npm --version) + if ! node -e "const [a,b]='$(npm --version)'.split('.').map(Number); process.exit(a>11||(a===11&&b>=15)?0:1)"; then + # Pinned exact version (supply-chain: never float @latest in the publish + # job); bump deliberately when a newer npm is required. + echo "npm $CUR < 11.15 — installing pinned npm 11.15.0 for staged publishing" + npm install -g --ignore-scripts npm@11.15.0 + fi + npm --version + + - name: Publish to npm (staged — owner approves with 2FA) + if: steps.resolve.outputs.skip != 'true' && (github.event_name != 'workflow_dispatch' || inputs.publish_mode != 'direct') env: VERSION: ${{ steps.resolve.outputs.version }} TAG: ${{ steps.resolve.outputs.tag }} @@ -175,10 +209,32 @@ jobs: run: | set -euo pipefail # Always pass --tag explicitly. Defense in depth: even if VERSION is - # accidentally an older release, `npm publish --tag historic` will - # NOT promote it to `@latest`. + # accidentally an older release, the historic tag will NOT claim `@latest`. + npm stage publish --provenance --access public --tag "$TAG" + { + echo "## 📦 omniroute@$VERSION STAGED (not yet installable)" + echo "" + echo "The exact bytes are parked on the registry. To release them:" + echo '```' + echo "npm stage list omniroute # find the stage id" + echo "npm stage approve # owner 2FA — THE publish" + echo '```' + echo "To verify the staged bytes first: npm stage download → run" + echo "scripts/check/check-pack-boot.mjs against them (see RELEASE_CHECKLIST)." + echo "To discard: npm stage reject ." + } >> "$GITHUB_STEP_SUMMARY" + echo "✅ Staged omniroute@$VERSION (dist-tag=$TAG) — awaiting owner 'npm stage approve'" + + - name: Publish to npm (DIRECT — emergency fallback) + if: steps.resolve.outputs.skip != 'true' && github.event_name == 'workflow_dispatch' && inputs.publish_mode == 'direct' + env: + VERSION: ${{ steps.resolve.outputs.version }} + TAG: ${{ steps.resolve.outputs.tag }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + set -euo pipefail npm publish --provenance --access public --tag "$TAG" - echo "✅ Published omniroute@$VERSION (dist-tag=$TAG)" + echo "✅ Published omniroute@$VERSION (dist-tag=$TAG) [DIRECT mode]" - name: Publish to GitHub Packages if: steps.resolve.outputs.skip != 'true' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index f6cdbe4d98..e54c4ac4c2 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -62,7 +62,7 @@ jobs: docs-gates: name: Docs Gates (fast-path) needs: changes - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && (needs.changes.outputs.docs == 'true' || needs.changes.outputs.code == 'true')) }} + if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && (needs.changes.outputs.docs == 'true' || needs.changes.outputs.code == 'true')) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -82,7 +82,7 @@ jobs: name: Fast Quality Gates needs: changes # Code surface only — pure docs/i18n PRs skip this bag (docs-gates covers docs). - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true') }} + if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} # Dynamic runner (same rule as ci.yml): use the self-hosted VPS pool only when the # release captain has USE_VPS_RUNNER=true AND this is not a fork PR (own-origin # branches only — a fork PR must never execute on the LAN runner). Var unset/false @@ -143,6 +143,24 @@ jobs: - run: npm run check:complexity-ratchets - name: Typecheck (core) run: npm run typecheck:core + # #7033: dashboard-scoped typecheck gate — src/app/(dashboard) TSX is not + # covered by typecheck:core's curated allowlist. See check-dashboard-typecheck.mjs. + - name: Typecheck (dashboard) + run: npm run check:dashboard-typecheck + # WS4.2 (v3.8.49 plan): TypeScript 7 native-compiler SHADOW — advisory only. + # TS7 went GA 2026-07-08 with 8-12x type-check speedups; its Compiler API only + # arrives in 7.1, so typescript-eslint / type-coverage / Stryker stay on 6.x + # (the hybrid is the officially documented pattern). Isolated npx on purpose: + # installing an alias package could collide node_modules/.bin/tsc with 6.x. + # Promote to the blocking gate after ~1 week of parity with the step above. + - name: Typecheck (core) — TS7 native shadow (advisory) + continue-on-error: true + run: | + RC=0 + START=$(date +%s) + npx -y -p typescript@7 tsc --pretty false -p tsconfig.typecheck-core.json || RC=$? + echo "[ts7-shadow] exit=$RC elapsed=$(( $(date +%s) - START ))s — the 6.x step above stays authoritative" + exit $RC # TIA: build the impact map at runtime (gitignored, ~21MB) and run only the # unit tests impacted by this PR's changed files. On hub/unmapped changes the # selector returns __RUN_ALL__ — full-suite authority is the parallel @@ -196,7 +214,7 @@ jobs: fast-vitest: name: Vitest (fast-path) needs: changes - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true') }} + if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-latest). runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} env: @@ -212,12 +230,23 @@ jobs: node-version: ${{ env.CI_NODE_VERSION }} cache: npm - run: npm ci - - run: npm run test:vitest + # WS5.2/5.3: JUnit feeds Trunk Flaky Tests — the fast-path runs on EVERY PR, + # which is where flaky-detection volume actually comes from (ci.yml's heavy + # jobs only run on the release PR). Advisory upload, own-origin only. + - run: npm run test:vitest -- --reporter=default --reporter=junit --outputFile.junit=trunk-junit/vitest-fastpath.xml + - name: Upload test results to Trunk (advisory) + if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + continue-on-error: true + uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 + with: + junit-paths: trunk-junit/**/*.xml + org-slug: omniroute + token: ${{ secrets.TRUNK_TOKEN }} fast-unit: name: Unit Tests fast-path (${{ matrix.shard }}/4) needs: changes - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true') }} + if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-latest). # This is the heaviest fast-path job; 4-way sharding (was 2, #6781) halves the # critical path again (~8.5min → ~4.5min on ubuntu-latest; ~2min on the 8-slot @@ -263,7 +292,7 @@ jobs: lint-guard: name: No new ESLint warnings needs: changes - if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.code == 'true') }} + if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} runs-on: ubuntu-latest continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} steps: @@ -302,7 +331,7 @@ jobs: merge-integrity: name: Merge integrity (changelog + generated skills) # Always on non-draft PRs — CHANGELOG/skills can break on docs-only merges too. - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) }} runs-on: ubuntu-latest continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} env: diff --git a/.gitignore b/.gitignore index 67d69b3d5d..b7406dbfca 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* !.env.example +!.env.homolog.example # Provider API keys (never commit) *.api-key .nvidia-api-key @@ -242,3 +243,9 @@ _artifacts/ # CI/local quality artifacts (eslint-results.json, etc.) .artifacts/ + +# Homologation E2E suite (npm run homolog) — real-environment credentials + report output +.env.homolog +tests/homolog/.auth/ +tests/homolog/ui/.auth/ +homolog-report/ diff --git a/.gitleaks.toml b/.gitleaks.toml index 0051e694b2..8b9978a454 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -74,3 +74,16 @@ # '''tests/unit/''', # ] # + +[[rules]] + # Falsos-positivos comprovados do generic-api-key — zerados em 2026-07-13 (WS6/D3, + # plano v3.8.49). Revisar em v3.9.0. Nenhum é credencial: dois são NOMES DE CAMPO + # de métricas de latência; o terceiro é o valor PÚBLICO de um beta header da API + # da Anthropic (documentado publicamente, não é segredo). + id = "generic-api-key" + [rules.allowlist] + description = "Field names + public Anthropic beta-header value (não são segredos)" + regexes = [ + '''latencyP\d{2}Ms''', + '''interleaved-thinking-2025-05-14''', + ] diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000000..131c6d71a9 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,55 @@ +# Mergify merge queue — WS3.4/D5 of the v3.8.49 quality/velocity master plan. +# +# WHY: ~85-100 active PR authors/month and 300+ PRs/week peaks, all merged by ONE +# identity. The manual merge-train validated batches by hand; this queue automates +# it with batching + automatic batch bisection (a red batch of N costs ~log2(N) +# revalidations instead of N). Mergify Open Source plan: free, unlimited, public repo. +# +# GOVERNANCE (non-negotiable, mirrors CLAUDE.md Hard Rules #21/#22 + the owner's +# pre-merge ⭐ gate): +# • A PR enters the queue ONLY via the `queue` label — applied by the owner (or a +# session acting for the owner) AFTER the pre-merge ⭐ report/decision. The label +# IS the merge approval; Mergify only executes it. +# • During a release-freeze (open issue labeled `release-freeze`), do NOT label PRs +# targeting the frozen branch — the freeze is a human-honored coordination signal +# the queue cannot see. Retarget to the active release/vX+1 first (Hard Rule #21). +# • Never label a PR another session is actively working (Hard Rule #22b). +# • Fallback path if Mergify misbehaves or the OSS plan changes: the manual +# merge-train runbook (docs/ops/MERGE_TRAIN.md) — remove labels, proceed by hand. + +queue_rules: + - name: release + # Any current or future release branch — the reason GitHub's native queue was + # rejected (no wildcard support on personal-account repos). + queue_conditions: + - base~=^release/v\d+\.\d+\.\d+$ + - label=queue + - -draft + - -conflict + # "Everything that ran is green, nothing still running, AND the always-on + # anchor check succeeded" — robust to the path-filtered fast-gates (docs-only + # PRs skip code jobs; matrix shard names vary) while never fail-open: a PR with + # zero checks cannot vacuously merge, because `Merge integrity` runs on EVERY + # non-draft PR (quality.yml) and must be an affirmative success. Review approval + # is intentionally NOT a condition here: the owner-applied `queue` label IS the + # approval in this repo's single-maintainer model (see governance header). + merge_conditions: + - "#check-failure=0" + - "#check-pending=0" + - "#check-success>=1" + - check-success=Merge integrity (changelog + generated skills) + # Batching: validate up to 10 queued PRs together (the manual train's sweet spot); + # don't hold a lone PR hostage waiting for siblings. + batch_size: 10 + batch_max_wait_time: 5 min + # Squash keeps the one-commit-per-PR history the CHANGELOG reconciliation expects. + merge_method: squash + +pull_request_rules: + - name: clean up the queue label after merge + conditions: + - merged + actions: + label: + remove: + - queue diff --git a/changelog.d/features/homolog-e2e-suite.md b/changelog.d/features/homolog-e2e-suite.md new file mode 100644 index 0000000000..b2c4637d66 --- /dev/null +++ b/changelog.d/features/homolog-e2e-suite.md @@ -0,0 +1 @@ +- **Homologation suite**: new `npm run homolog` runs the full release-homologation battery against the deployed VPS — health/version parity, API + real SSE streaming with an ephemeral API key (created and revoked by the run), minimal-cost real-provider smoke (promptfoo generated from the live catalog), and a Playwright sweep that loads every dashboard route and exercises the API-key UI flow — emitting a unified CTRF report that backs the release STOP #2 checklist diff --git a/changelog.d/fixes/5716-proxy-pool-relayauth-dropped.md b/changelog.d/fixes/5716-proxy-pool-relayauth-dropped.md new file mode 100644 index 0000000000..72d11d1f3a --- /dev/null +++ b/changelog.d/fixes/5716-proxy-pool-relayauth-dropped.md @@ -0,0 +1 @@ +- fix(providers): preserve relayAuth for vercel/deno/cloudflare relay proxies referenced by-id from the no-auth-provider Proxy Pool dropdown (#5716) diff --git a/changelog.d/fixes/6612-combo-diagnostics-header-bytestring-crash.md b/changelog.d/fixes/6612-combo-diagnostics-header-bytestring-crash.md new file mode 100644 index 0000000000..77f12ced1f --- /dev/null +++ b/changelog.d/fixes/6612-combo-diagnostics-header-bytestring-crash.md @@ -0,0 +1 @@ +- fix(sse): sanitize non-Latin1 characters before embedding combo diagnostics in HTTP headers, preventing a ByteString crash on quality-check failure (#6612) diff --git a/changelog.d/fixes/6699-jules-chat-executor-misroute.md b/changelog.d/fixes/6699-jules-chat-executor-misroute.md new file mode 100644 index 0000000000..58823d5879 --- /dev/null +++ b/changelog.d/fixes/6699-jules-chat-executor-misroute.md @@ -0,0 +1 @@ +- fix(providers): reject chat-completions requests for cloud-agent-only providers like jules instead of silently mis-routing them to OpenAI's endpoint (#6699) diff --git a/changelog.d/fixes/6835-db-oom-probe-cyclebreaker.md b/changelog.d/fixes/6835-db-oom-probe-cyclebreaker.md new file mode 100644 index 0000000000..3208171d3f --- /dev/null +++ b/changelog.d/fixes/6835-db-oom-probe-cyclebreaker.md @@ -0,0 +1 @@ +- **fix(db):** cap the sql.js OOM-during-probe path in `getDbInstance()` at 3 attempts with a terminal diagnostic — previously only the generic-corruption probe-failure path had a cycle-breaker (#6632), so a persistently OOMing `storage.sqlite` probe re-threw the identical error forever on every call from every background poller, hanging the app with "Internal Server Error" and no self-recovery (#6835). diff --git a/changelog.d/fixes/6996-duckduckgo-vqd-429-misclassified.md b/changelog.d/fixes/6996-duckduckgo-vqd-429-misclassified.md new file mode 100644 index 0000000000..36b15a9cbd --- /dev/null +++ b/changelog.d/fixes/6996-duckduckgo-vqd-429-misclassified.md @@ -0,0 +1 @@ +- fix(providers): DuckDuckGo AI Chat executor propagates the real upstream status (429 rate limit with `Retry-After`) instead of misclassifying VQD-token acquisition failures as a hardcoded 503 (#6996) diff --git a/changelog.d/fixes/6998-opencode-oc-free-tier-catalog.md b/changelog.d/fixes/6998-opencode-oc-free-tier-catalog.md new file mode 100644 index 0000000000..e5a3f5189b --- /dev/null +++ b/changelog.d/fixes/6998-opencode-oc-free-tier-catalog.md @@ -0,0 +1 @@ +- fix(providers): refresh OpenCode (`oc`) free-tier model catalog — 6 delisted IDs replaced with the 4 currently-live free models (#6998) diff --git a/changelog.d/fixes/7003-jetbrains-ai-loopback-connect.md b/changelog.d/fixes/7003-jetbrains-ai-loopback-connect.md new file mode 100644 index 0000000000..bc3b0a89c4 --- /dev/null +++ b/changelog.d/fixes/7003-jetbrains-ai-loopback-connect.md @@ -0,0 +1 @@ +- fix(api): raise the main server's `keepAliveTimeout`/`headersTimeout` well above Node's 5s default so pooled keep-alive clients (e.g. JetBrains AI Assistant's JVM `HttpClient`) stop getting 0 bytes back on a reused connection (#7003) diff --git a/changelog.d/fixes/7005-adaptive-context-budget-dial.md b/changelog.d/fixes/7005-adaptive-context-budget-dial.md new file mode 100644 index 0000000000..4b8e8aee5b --- /dev/null +++ b/changelog.d/fixes/7005-adaptive-context-budget-dial.md @@ -0,0 +1 @@ +- fix(compression): wire the adaptive context-budget "dial" (`contextBudget`) into the settings schema and DB so it can actually be persisted via `PUT /api/settings/compression`, instead of being silently rejected (#7005) diff --git a/changelog.d/fixes/7022-opencode-go-quota-url-zai.md b/changelog.d/fixes/7022-opencode-go-quota-url-zai.md new file mode 100644 index 0000000000..e136c66551 --- /dev/null +++ b/changelog.d/fixes/7022-opencode-go-quota-url-zai.md @@ -0,0 +1 @@ +- fix(usage): stop opencode-go quota lookup from defaulting to an unrelated Z.AI endpoint (#7022) diff --git a/changelog.d/fixes/7033-dashboard-typecheck-gate.md b/changelog.d/fixes/7033-dashboard-typecheck-gate.md new file mode 100644 index 0000000000..89a33f44d6 --- /dev/null +++ b/changelog.d/fixes/7033-dashboard-typecheck-gate.md @@ -0,0 +1 @@ +- fix(ci): add a dashboard-scoped typecheck gate covering `src/app/(dashboard)` TSX, previously invisible to `typecheck:core` and `next build` (#7033) diff --git a/changelog.d/fixes/7051-turbopack-compression-ignoreissue.md b/changelog.d/fixes/7051-turbopack-compression-ignoreissue.md new file mode 100644 index 0000000000..017b5b8c78 --- /dev/null +++ b/changelog.d/fixes/7051-turbopack-compression-ignoreissue.md @@ -0,0 +1 @@ +- fix(build): extend the Turbopack `ignoreIssue` suppression to `open-sse/services/compression/**`, matching the `getModuleDir()` dynamic-path fs pattern already suppressed for `src/lib/agentSkills/**` in #6582, eliminating the remaining 610 "Overly broad patterns" warnings (#7051) diff --git a/changelog.d/fixes/7058-zai-web-proxy-connection-test.md b/changelog.d/fixes/7058-zai-web-proxy-connection-test.md new file mode 100644 index 0000000000..f7d453d734 --- /dev/null +++ b/changelog.d/fixes/7058-zai-web-proxy-connection-test.md @@ -0,0 +1 @@ +- fix(providers): web-cookie connection-test/cookie-validation probe (zai-web and every other registry-entry web-cookie provider) now honors the configured HTTP/SOCKS proxy — the `/models` probe routed through `directHttpsRequest`'s hardcoded native-fetch bypass, silently skipping proxy resolution even though the executor's actual chat traffic already respected it (#7058) diff --git a/changelog.d/fixes/7071-ollama-cloud-session-quota.md b/changelog.d/fixes/7071-ollama-cloud-session-quota.md new file mode 100644 index 0000000000..81807e30ad --- /dev/null +++ b/changelog.d/fixes/7071-ollama-cloud-session-quota.md @@ -0,0 +1 @@ +- fix(resilience): recognize Ollama Cloud's 5-hour session usage-limit 429 as quota-exhausted instead of a generic rate limit (#7071) diff --git a/changelog.d/fixes/7072-quota-card-grid-mobile.md b/changelog.d/fixes/7072-quota-card-grid-mobile.md new file mode 100644 index 0000000000..df74be8a75 --- /dev/null +++ b/changelog.d/fixes/7072-quota-card-grid-mobile.md @@ -0,0 +1 @@ +- fix(dashboard): restore mobile single-column fallback on the Provider Quota page card grid, fixing clipped labels/buttons on phone-width viewports (#7072) diff --git a/changelog.d/fixes/7080-proxy-test-connection-saved-proxyid.md b/changelog.d/fixes/7080-proxy-test-connection-saved-proxyid.md new file mode 100644 index 0000000000..cc374b6a8a --- /dev/null +++ b/changelog.d/fixes/7080-proxy-test-connection-saved-proxyid.md @@ -0,0 +1 @@ +- fix(dashboard): include proxyId when testing a saved registry proxy so SOCKS5/auth credentials are loaded (#7080) diff --git a/changelog.d/fixes/7134-claude-web-400-no-response-body.md b/changelog.d/fixes/7134-claude-web-400-no-response-body.md new file mode 100644 index 0000000000..f9fd94cf5f --- /dev/null +++ b/changelog.d/fixes/7134-claude-web-400-no-response-body.md @@ -0,0 +1 @@ +- **fix(sse):** claude-web now surfaces the real upstream error body for non-SSE 400/403/429/500 responses instead of reporting "no response body" — the streaming client was discarding the already-captured temp-file bytes and reading the native binding's empty in-memory body field instead (#7134). diff --git a/changelog.d/fixes/7149-combo-scope-proxy-dead.md b/changelog.d/fixes/7149-combo-scope-proxy-dead.md new file mode 100644 index 0000000000..fcbbccb065 --- /dev/null +++ b/changelog.d/fixes/7149-combo-scope-proxy-dead.md @@ -0,0 +1 @@ +- fix(db): honor combo-level proxy assignments from the registry when resolving a connection's proxy (#7149) diff --git a/changelog.d/fixes/7151-hermes-agent-model-aliases.md b/changelog.d/fixes/7151-hermes-agent-model-aliases.md new file mode 100644 index 0000000000..b408b03462 --- /dev/null +++ b/changelog.d/fixes/7151-hermes-agent-model-aliases.md @@ -0,0 +1 @@ +- fix(dashboard): wire modelAliases fetch into HermesAgentToolCard so OpenRouter and other passthrough providers appear in the Hermes Agent role picker (#7151) diff --git a/changelog.d/fixes/7156-legacy-model-picker-hidden-filter.md b/changelog.d/fixes/7156-legacy-model-picker-hidden-filter.md new file mode 100644 index 0000000000..cb3edb1642 --- /dev/null +++ b/changelog.d/fixes/7156-legacy-model-picker-hidden-filter.md @@ -0,0 +1 @@ +- fix(dashboard): filter hidden custom models out of the legacy combo model picker (#7156) diff --git a/changelog.d/fixes/7157-agent-bridge-dns-405.md b/changelog.d/fixes/7157-agent-bridge-dns-405.md new file mode 100644 index 0000000000..98a887ce2a --- /dev/null +++ b/changelog.d/fixes/7157-agent-bridge-dns-405.md @@ -0,0 +1 @@ +- fix(dashboard): Agent Bridge DNS toggle now sends POST (was PUT), fixing HTTP 405 on Start/Stop DNS (#7157) diff --git a/changelog.d/fixes/7161-free-pool-handletogglesource.md b/changelog.d/fixes/7161-free-pool-handletogglesource.md new file mode 100644 index 0000000000..b85d16d651 --- /dev/null +++ b/changelog.d/fixes/7161-free-pool-handletogglesource.md @@ -0,0 +1 @@ +- fix(dashboard): implement missing `handleToggleSource` callback on the Free Pool tab so the page no longer crashes with a `ReferenceError` (#7161) diff --git a/changelog.d/fixes/7163-gemini-web-duplicated-text.md b/changelog.d/fixes/7163-gemini-web-duplicated-text.md new file mode 100644 index 0000000000..6bac17d7e8 --- /dev/null +++ b/changelog.d/fixes/7163-gemini-web-duplicated-text.md @@ -0,0 +1 @@ +- fix(sse): stop duplicating text in Gemini Web streamed responses (#7163) diff --git a/changelog.d/fixes/pack-boot-runtimetimeouts-sibling.md b/changelog.d/fixes/pack-boot-runtimetimeouts-sibling.md new file mode 100644 index 0000000000..3fd32f4f02 --- /dev/null +++ b/changelog.d/fixes/pack-boot-runtimetimeouts-sibling.md @@ -0,0 +1 @@ +- **Build**: the packed tarball boots again — #7191's `../../src/…runtimeTimeouts.ts` import in `standalone-server-ws.mjs` escaped the package after the dist-root copy (`ERR_MODULE_NOT_FOUND` on every boot, #7065 class, caught live by the new `check:pack-boot` gate); the helper now lives in the shipped sibling `main-server-timeouts.mjs` (parity-tested against the canonical TS implementation) and the closure test bans package-escaping `../` imports in npm-shipped wrappers diff --git a/changelog.d/fixes/pack-boot-smoke-gate.md b/changelog.d/fixes/pack-boot-smoke-gate.md new file mode 100644 index 0000000000..cc44649218 --- /dev/null +++ b/changelog.d/fixes/pack-boot-smoke-gate.md @@ -0,0 +1 @@ +- **Packaging**: new `check:pack-boot` gate packs the real npm tarball, installs it into a clean prefix (postinstall runs for real) and boots the installed CLI until `/api/monitoring/health` returns 200 with the packed version — the runtime gate that structure checks could not provide (3 releases shipped boot-crashing tarballs with green packaging lists: tls-options/3.8.41, #7040, #7065). Wired into the CI `package-artifact` job and `check:release-green --with-build` diff --git a/changelog.d/fixes/pack-entrypoint-closures.md b/changelog.d/fixes/pack-entrypoint-closures.md new file mode 100644 index 0000000000..09e114614d --- /dev/null +++ b/changelog.d/fixes/pack-entrypoint-closures.md @@ -0,0 +1 @@ +- **Packaging**: pack-artifact closure tests now cover EVERY npm-shipped dist wrapper (derived from `EXTRA_MODULE_ENTRIES`) and the `bin/omniroute.mjs` CLI entry — including dynamic `import()` and `require()` forms the original server-ws test missed — requiring each local import in both the prepublish prune allowlist and `check:pack-artifact`; `bin/cli/data-dir.mjs` and `bin/cli/utils/storageKeyProvision.mjs` are now required tarball paths (#7065 class hardening, WS1.1) diff --git a/changelog.d/fixes/register-cli-skill-collector.md b/changelog.d/fixes/register-cli-skill-collector.md new file mode 100644 index 0000000000..f7ba19ba92 --- /dev/null +++ b/changelog.d/fixes/register-cli-skill-collector.md @@ -0,0 +1 @@ +- **Skills**: register `cli-skill-collector` in the agent-skills catalog (types union, curated entry, CLI id list) — #6294 shipped the `skills/cli-skill-collector/` directory without the catalog registration, so it was unreachable via the API and Integration CI failed on the catalog-integrity test; counts aligned (44 API+CLI, 45 with config) diff --git a/changelog.d/fixes/yuanbao-cookie-validation.md b/changelog.d/fixes/yuanbao-cookie-validation.md new file mode 100644 index 0000000000..eb91b6edba --- /dev/null +++ b/changelog.d/fixes/yuanbao-cookie-validation.md @@ -0,0 +1 @@ +- **Providers**: yuanbao-web no longer forwards a foreign single cookie pair upstream — `buildYuanbaoCookie` only trusts `hy_user`/`hy_token` extractions the input explicitly names, so a missing session token now fails fast with the local 401 guidance instead of a live Tencent round-trip diff --git a/changelog.d/maintenance/7068-dependabot-ignore-typescript-major.md b/changelog.d/maintenance/7068-dependabot-ignore-typescript-major.md new file mode 100644 index 0000000000..ae20696c5e --- /dev/null +++ b/changelog.d/maintenance/7068-dependabot-ignore-typescript-major.md @@ -0,0 +1 @@ +- **chore(ci):** stop dependabot from proposing `typescript` majors — `typescript-eslint` pins a hard peer upper bound (`>=4.8.4 <6.1.0`), so a TS 7 bump violates the peer and takes the whole toolchain red at once. #7068 grouped it with 6 harmless dev bumps and blocked all of them. TS majors now migrate intentionally, in their own PR. diff --git a/changelog.d/maintenance/7291-quota-card-grid-density-6815-guard.md b/changelog.d/maintenance/7291-quota-card-grid-density-6815-guard.md new file mode 100644 index 0000000000..6652969e7d --- /dev/null +++ b/changelog.d/maintenance/7291-quota-card-grid-density-6815-guard.md @@ -0,0 +1 @@ +- **test(dashboard):** restore dedicated regression coverage for #6815's `QuotaCardGrid` multi-column density guarantee, decoupled from the specific Tailwind token so it survives the #7027 auto-fit migration ([#7291](https://github.com/diegosouzapw/OmniRoute/pull/7291)) diff --git a/changelog.d/maintenance/7307-rehome-open-prs-script.md b/changelog.d/maintenance/7307-rehome-open-prs-script.md new file mode 100644 index 0000000000..e20161f4bc --- /dev/null +++ b/changelog.d/maintenance/7307-rehome-open-prs-script.md @@ -0,0 +1 @@ +- **chore(release):** add `scripts/release/rehome-open-prs.mjs` — the Phase 0a.0b PR re-home, scripted with a read-back after every retarget. `gh pr edit --base` exits 0 without applying (v3.8.42), `gh pr list` silently caps at 30, and the v3.8.49 freeze had 148 open PRs to move — none of which a hand-run loop survives reliably. diff --git a/changelog.d/maintenance/codecov-patch-coverage.md b/changelog.d/maintenance/codecov-patch-coverage.md new file mode 100644 index 0000000000..6d023220d6 --- /dev/null +++ b/changelog.d/maintenance/codecov-patch-coverage.md @@ -0,0 +1 @@ +- **CI**: Codecov patch-coverage on every PR diff (informational during calibration — `codecov.yml` sets nothing blocking; strict-patch/lenient-project philosophy on top of the existing 60% c8 floor + ratchet); the CI coverage job now actually emits `coverage/lcov.info` (the `lcov` reporter was missing, so the artifact silently skipped it — the same file Sonar consumes) diff --git a/changelog.d/maintenance/coverage-job-timeout.md b/changelog.d/maintenance/coverage-job-timeout.md new file mode 100644 index 0000000000..ce36f15f3f --- /dev/null +++ b/changelog.d/maintenance/coverage-job-timeout.md @@ -0,0 +1 @@ +- **CI**: raise the Coverage job timeout 10→20min — the lcov reporter added for Codecov/Sonar (#7114) pushed the 8-shard report merge past the old cap, and three release-tip runs died at exactly 10min as job-timeout "cancelled" diff --git a/changelog.d/maintenance/dast-smoke-timeout.md b/changelog.d/maintenance/dast-smoke-timeout.md new file mode 100644 index 0000000000..b8c9880570 --- /dev/null +++ b/changelog.d/maintenance/dast-smoke-timeout.md @@ -0,0 +1 @@ +- **CI**: raise the dast-smoke job timeout 12→25min — the CLI bundle build alone varies 6-11min on GitHub-hosted runners, so the old cap killed Schemathesis mid-run (3 consecutive false-negative timeouts on 2026-07-14) diff --git a/changelog.d/maintenance/e2e-shard-balance.md b/changelog.d/maintenance/e2e-shard-balance.md new file mode 100644 index 0000000000..a950f1ba44 --- /dev/null +++ b/changelog.d/maintenance/e2e-shard-balance.md @@ -0,0 +1 @@ +- **CI**: E2E matrix shards are now duration-balanced (LPT bin-packing over `config/quality/e2e-timings.json`) instead of Playwright's count-based `--shard` — measured skew was 14× (24m47s vs 1m47s), making E2E the CI critical path; the balancer self-verifies that every spec lands in exactly one shard and falls back to plain `--shard` on any inconsistency diff --git a/changelog.d/maintenance/electron-win-advisory.md b/changelog.d/maintenance/electron-win-advisory.md new file mode 100644 index 0000000000..c5f29e8404 --- /dev/null +++ b/changelog.d/maintenance/electron-win-advisory.md @@ -0,0 +1 @@ +- **CI**: the new Electron Windows prepare-bundle leg (WS1.5) is advisory while it matures — its first real run failed with the error swallowed by pwsh; the step now runs under bash (stderr captured) with `continue-on-error`, tracked for promotion once green diff --git a/changelog.d/maintenance/electron-win-prepare-smoke.md b/changelog.d/maintenance/electron-win-prepare-smoke.md new file mode 100644 index 0000000000..2fcdfe3740 --- /dev/null +++ b/changelog.d/maintenance/electron-win-prepare-smoke.md @@ -0,0 +1 @@ +- **CI**: the Electron Package Smoke job now runs a Windows leg that executes `prepare:bundle` (the native ABI rebuild + spawn plan) per release PR — the v3.8.48 Windows bug (`npx.cmd` spawned without shell) could previously only surface on the release tag, its first-ever execution diff --git a/changelog.d/maintenance/gate-hygiene-secrets-semgrep-hadolint.md b/changelog.d/maintenance/gate-hygiene-secrets-semgrep-hadolint.md new file mode 100644 index 0000000000..7009f36ab2 --- /dev/null +++ b/changelog.d/maintenance/gate-hygiene-secrets-semgrep-hadolint.md @@ -0,0 +1 @@ +- **Quality gates hygiene (WS6/D3 + WS1.7)**: gitleaks baseline zeroed — the 3 frozen `generic-api-key` false positives (latency field names + the public Anthropic beta-header value) are allowlisted with justification, so any NEW secret finding now regresses the ratchet from 0; the orphaned `semgrepFindings` baseline metric was dropped (never wired to a gate; CodeQL covers the OWASP families); Dockerfile now has a hadolint gate in the lint job (digest-pinned, error-threshold — the 5 pre-existing warnings stay visible without blocking) diff --git a/changelog.d/maintenance/hotfix-fastlane.md b/changelog.d/maintenance/hotfix-fastlane.md new file mode 100644 index 0000000000..a7110c9a79 --- /dev/null +++ b/changelog.d/maintenance/hotfix-fastlane.md @@ -0,0 +1 @@ +- **CI**: hotfix fast-lane — PRs labeled `hotfix` (owner-applied, production-broken only; entry policy in `docs/ops/RELEASE_CHECKLIST.md`) skip the 9-shard E2E matrix, coverage ratchet and extended gates while keeping build, unit/integration/vitest, lint/typecheck and the tarball boot-smoke (~15min instead of ~33min); tests-only diffs outside `tests/e2e/` skip the E2E matrix automatically via the new `testsOnly` change-classification output diff --git a/changelog.d/maintenance/mergify-queue-draft-ci.md b/changelog.d/maintenance/mergify-queue-draft-ci.md new file mode 100644 index 0000000000..63469d2c33 --- /dev/null +++ b/changelog.d/maintenance/mergify-queue-draft-ci.md @@ -0,0 +1 @@ +- **CI**: quality.yml draft guards now also match Mergify speculative merge-queue PRs (`mergify/merge-queue/*` heads are drafts by design) — without this every queued batch failed its anchor check in 2s and dequeued diff --git a/changelog.d/maintenance/mergify-queue.md b/changelog.d/maintenance/mergify-queue.md new file mode 100644 index 0000000000..8ffe6bb43a --- /dev/null +++ b/changelog.d/maintenance/mergify-queue.md @@ -0,0 +1 @@ +- **Merge queue (D5)**: reviewed PRs now merge through the Mergify queue (`.mergify.yml`, Open Source plan) — entry is the owner-applied `queue` label AFTER the pre-merge ⭐ gate; batches of up to 10 validate together with automatic bisection of red batches (~log2(N) instead of N revalidations); the manual merge-train is codified as the fallback runbook in `docs/ops/MERGE_TRAIN.md` with the freeze/cross-session guardrails diff --git a/changelog.d/maintenance/npm-staged-publishing.md b/changelog.d/maintenance/npm-staged-publishing.md new file mode 100644 index 0000000000..e57f471083 --- /dev/null +++ b/changelog.d/maintenance/npm-staged-publishing.md @@ -0,0 +1 @@ +- **Release**: npm publishing is now STAGED by default — the workflow boots the packed tarball (`check:pack-boot`) and runs `npm stage publish` (bytes parked on the registry, not installable); the owner verifies the staged bytes and releases them with `npm stage approve` + 2FA, moving the human gate to after the proof (the structural fix for the #7065 broken-tarball class); `publish_mode=direct` remains as a documented emergency fallback diff --git a/changelog.d/maintenance/quality-policies-ws5.md b/changelog.d/maintenance/quality-policies-ws5.md new file mode 100644 index 0000000000..990c7e7c20 --- /dev/null +++ b/changelog.d/maintenance/quality-policies-ws5.md @@ -0,0 +1 @@ +- **Docs**: `QUALITY_GATES.md` now codifies the per-runner test retry policy (Playwright 1 CI retry with trace; Vitest per-test explicit quarantine only; node:test never) with target flake SLOs, and the release-level ratchet-drift rule (combination drift on the pure tip is the release captain's to fix once on the branch — never pushed onto contributor PRs, never rebaselined per-PR) diff --git a/changelog.d/maintenance/release-green-continuous.md b/changelog.d/maintenance/release-green-continuous.md new file mode 100644 index 0000000000..b3c181510d --- /dev/null +++ b/changelog.d/maintenance/release-green-continuous.md @@ -0,0 +1 @@ +- **CI**: release-green validation is now continuous — every code push to `release/v*` (including the captain's direct sync-back pushes, the one previously ungated write path) triggers a `--quick` HARD-gate run with per-branch superseded-run cancellation, and the tracking issue names the offending push range; the deep `--with-build --full-ci` sweep now runs 3×/day instead of nightly-only (base-red MTTD: ≤24h → ~15min after the offending push) diff --git a/changelog.d/maintenance/runner-janitor.md b/changelog.d/maintenance/runner-janitor.md new file mode 100644 index 0000000000..365f4a5b00 --- /dev/null +++ b/changelog.d/maintenance/runner-janitor.md @@ -0,0 +1 @@ +- **Ops**: `scripts/ops/runner-janitor.sh` + `docs/ops/RUNNER_BOX.md` codify the self-hosted runner box hygiene that was manual discipline — 30min cron sweeping stale runner temp dirs, alerting at ≥85% disk, and enforcing the proven 4-runner ceiling on the 16 GB box (8-wide OOM-killed jobs twice on the v3.8.47 release day) diff --git a/changelog.d/maintenance/selfref-6634-shallow.md b/changelog.d/maintenance/selfref-6634-shallow.md new file mode 100644 index 0000000000..6f0c1b345a --- /dev/null +++ b/changelog.d/maintenance/selfref-6634-shallow.md @@ -0,0 +1 @@ +- **Tests**: the #6634 self-reference test now fetches `origin/main` on demand and skips cleanly when the ref is unreachable — it failed as a false positive on shallow/single-ref checkouts (GitHub-hosted runners) diff --git a/changelog.d/maintenance/sync-back-green-gate.md b/changelog.d/maintenance/sync-back-green-gate.md new file mode 100644 index 0000000000..40bded1502 --- /dev/null +++ b/changelog.d/maintenance/sync-back-green-gate.md @@ -0,0 +1 @@ +- **Release tooling**: `sync-next-cycle.mjs` now runs `validate-release-green --quick` on the merged tree BEFORE pushing the parallel-cycle sync-back — the one write path to the release branch that had no CI gate; a red merged tree stays local instead of turning the whole PR queue red (`--skip-green-gate` is the documented emergency hatch) diff --git a/changelog.d/maintenance/tighten-coverage-baseline.md b/changelog.d/maintenance/tighten-coverage-baseline.md new file mode 100644 index 0000000000..119eb1c856 --- /dev/null +++ b/changelog.d/maintenance/tighten-coverage-baseline.md @@ -0,0 +1 @@ +- **chore(quality):** tighten the coverage ratchet to the CI's real numbers (branches 73→78.1, statements/lines 76.5→80.8, functions 82→86.44, plus 7 per-module floors). The gate had been asking for this in plain text; the values come from the merged-coverage run on `main`, not a local run (local measures ~68% vs CI's ~80% — the baseline's own note warns about that gap). diff --git a/changelog.d/maintenance/trunk-flaky-uploads.md b/changelog.d/maintenance/trunk-flaky-uploads.md new file mode 100644 index 0000000000..89c0e6b853 --- /dev/null +++ b/changelog.d/maintenance/trunk-flaky-uploads.md @@ -0,0 +1 @@ +- **CI**: Playwright E2E and both vitest suites now emit JUnit and upload to Trunk Flaky Tests (org `omniroute`) — advisory step, own-origin only, uploader action SHA-pinned (WS5.2/5.3 of the quality plan; node:test stays out of the first wave) diff --git a/changelog.d/maintenance/trunk-upload-fastpath.md b/changelog.d/maintenance/trunk-upload-fastpath.md new file mode 100644 index 0000000000..441f1300fe --- /dev/null +++ b/changelog.d/maintenance/trunk-upload-fastpath.md @@ -0,0 +1 @@ +- **CI**: the fast-path Vitest job (every PR) now also emits JUnit and uploads to Trunk Flaky Tests — the heavy-gate uploads alone (release PR only) would never accumulate flaky-detection volume diff --git a/changelog.d/maintenance/ts7-shadow-typecheck.md b/changelog.d/maintenance/ts7-shadow-typecheck.md new file mode 100644 index 0000000000..e73b70f8de --- /dev/null +++ b/changelog.d/maintenance/ts7-shadow-typecheck.md @@ -0,0 +1 @@ +- **CI**: TypeScript 7 (native compiler, GA 2026-07-08) now runs as an advisory SHADOW of the blocking `typecheck:core` gate on the fast path — same tsconfig, isolated `npx` (no dependency change; the Compiler API only lands in TS 7.1, so typescript-eslint/type-coverage/Stryker stay on 6.x). Local parity proven (0 errors on both, exit 0); promotion to the blocking gate after ~1 week of CI parity diff --git a/changelog.d/maintenance/verify-published.md b/changelog.d/maintenance/verify-published.md new file mode 100644 index 0000000000..3f244bfd73 --- /dev/null +++ b/changelog.d/maintenance/verify-published.md @@ -0,0 +1 @@ +- **Release tooling**: new `scripts/release/verify-published.mjs ` — post-publish net that installs the published version from the public registry inside a clean `node:24-slim` container and boots it until `/api/monitoring/health` reports the expected version (validates the exact bytes users install, on a machine with no repo/devbox state); wired into the release Phase 4 monitoring playbook diff --git a/changelog.d/maintenance/vitest-ui-blocking.md b/changelog.d/maintenance/vitest-ui-blocking.md new file mode 100644 index 0000000000..4ed70f7f3c --- /dev/null +++ b/changelog.d/maintenance/vitest-ui-blocking.md @@ -0,0 +1 @@ +- **CI**: promote `test:vitest:ui` to a blocking gate — the suite is 870/870 green again after the WS6.1 triage (#7127), so `continue-on-error` is removed from the vitest job diff --git a/changelog.d/maintenance/vitest-ui-suite-green.md b/changelog.d/maintenance/vitest-ui-suite-green.md new file mode 100644 index 0000000000..539058bd72 --- /dev/null +++ b/changelog.d/maintenance/vitest-ui-suite-green.md @@ -0,0 +1 @@ +- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..6f6d05d0f5 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,19 @@ +# Codecov — WS5.6/D7 of the v3.8.49 quality/velocity plan. +# Philosophy: strict patch, lenient project — the project floor/ratchet already +# lives in quality-baseline.json + the c8 60% gate; Codecov adds the DIFF view +# ("new lines in this PR are covered"), which the global ratchet cannot see. +# INFORMATIONAL during calibration: nothing here blocks a PR. Promote by flipping +# informational to false after ~2 weeks without false blocks (owner decision). +coverage: + status: + project: + default: + informational: true + patch: + default: + target: 70% + informational: true +comment: + layout: "condensed_header, diff" + behavior: default + require_changes: true diff --git a/config/quality/dashboard-typecheck-baseline.json b/config/quality/dashboard-typecheck-baseline.json new file mode 100644 index 0000000000..4f7857d6ce --- /dev/null +++ b/config/quality/dashboard-typecheck-baseline.json @@ -0,0 +1,259 @@ +{ + "open-sse/services/payloadRules.ts": { + "TS2677": 1 + }, + "src/app/(dashboard)/dashboard/HomePageClient.tsx": { + "TS2339": 16 + }, + "src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx": { + "TS2503": 3 + }, + "src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/agent-skills/components/McpA2aLinksBar.tsx": { + "TS2503": 2 + }, + "src/app/(dashboard)/dashboard/agent-skills/components/SkillCard.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane.tsx": { + "TS2503": 2 + }, + "src/app/(dashboard)/dashboard/cache/__tests__/CachePage.test.tsx": { + "TS2305": 3, + "TS1117": 1 + }, + "src/app/(dashboard)/dashboard/cache/__tests__/CachePerformance.test.tsx": { + "TS2305": 1, + "TS2322": 2 + }, + "src/app/(dashboard)/dashboard/cache/__tests__/CacheTrends.test.tsx": { + "TS2305": 1, + "TS2322": 6 + }, + "src/app/(dashboard)/dashboard/cache/__tests__/IdempotencyLayer.test.tsx": { + "TS2305": 1 + }, + "src/app/(dashboard)/dashboard/cache/__tests__/MemoryCards.test.tsx": { + "TS2305": 1, + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/cache/components/CachePerformance.tsx": { + "TS2339": 1 + }, + "src/app/(dashboard)/dashboard/cli-code/components/ClaudeToolCard.tsx": { + "TS2339": 2 + }, + "src/app/(dashboard)/dashboard/cli-code/components/CodexToolCard.tsx": { + "TS2345": 3 + }, + "src/app/(dashboard)/dashboard/cli-code/components/CustomCliCard.tsx": { + "TS2345": 1 + }, + "src/app/(dashboard)/dashboard/cli-code/components/DroidToolCard.tsx": { + "TS2554": 2 + }, + "src/app/(dashboard)/dashboard/combos/page.tsx": { + "TS2339": 4, + "TS2345": 5, + "TS2698": 1, + "TS2322": 13 + }, + "src/app/(dashboard)/dashboard/compression/studio/EncoderComparisonTable.tsx": { + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": { + "TS2304": 1 + }, + "src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx": { + "TS2551": 7, + "TS2322": 2, + "TS2719": 2, + "TS2739": 1 + }, + "src/app/(dashboard)/dashboard/costs/quota-share/components/StackedAllocationBar.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/costs/quota-share/components/UsageLogCard.tsx": { + "TS2869": 2 + }, + "src/app/(dashboard)/dashboard/discovery/__tests__/DiscoveryPageClient.test.tsx": { + "TS2305": 2 + }, + "src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": { + "TS2322": 18 + }, + "src/app/(dashboard)/dashboard/memory/components/tabs/MemoriesTab.tsx": { + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/OmniSkillsPageClient.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/OmniExecutionsTab.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/OmniMarketplaceTab.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/OmniSandboxTab.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/OmniSkillCard.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/OmniSkillsList.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/omni-skills/components/SkillInspectorPane.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx": { + "TS2352": 1 + }, + "src/app/(dashboard)/dashboard/playground/components/ToolsBuilder.tsx": { + "TS2339": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": { + "TS2322": 4 + }, + "src/app/(dashboard)/dashboard/providers/[id]/__tests__/phase1e.test.tsx": { + "TS2741": 2 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleModelsSection.tsx": { + "TS2741": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionRow.tsx": { + "TS2345": 3, + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionsListPanel.tsx": { + "TS2322": 2 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/CustomModelsSection.tsx": { + "TS2739": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ModelCompatPopover.tsx": { + "TS2304": 5 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx": { + "TS2322": 3, + "TS2739": 1, + "TS2345": 3 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModelsSection.tsx": { + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ProviderParamFilterSection.tsx": { + "TS2339": 6 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ProviderPlaygroundPanel.tsx": { + "TS2503": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/__tests__/phase1d.test.tsx": { + "TS2739": 2 + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": { + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelImportHandlers.ts": { + "TS2339": 1 + }, + "src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelVisibilityHandlers.ts": { + "TS2339": 15 + }, + "src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingCatalog.ts": { + "TS2339": 4, + "TS2345": 2 + }, + "src/app/(dashboard)/dashboard/providers/providerPageUtils.ts": { + "TS2345": 1 + }, + "src/app/(dashboard)/dashboard/providers/services/components/CliproxyModelMappingEditor.tsx": { + "TS2339": 1 + }, + "src/app/(dashboard)/dashboard/quota/page.tsx": { + "TS2339": 4 + }, + "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": { + "TS2304": 1 + }, + "src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": { + "TS2339": 4 + }, + "src/app/(dashboard)/dashboard/settings/components/RedisLauncherPanel.tsx": { + "TS2345": 11 + }, + "src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx": { + "TS2322": 1 + }, + "src/app/(dashboard)/dashboard/settings/components/proxy/FreePoolTab.tsx": { + "TS2304": 1 + }, + "src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CustomHostsManager.tsx": { + "TS2339": 1 + }, + "src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/ResponseBodyTab.tsx": { + "TS2339": 5 + }, + "src/app/(dashboard)/dashboard/translator/components/MonitorTab.tsx": { + "TS2345": 1 + }, + "src/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion.tsx": { + "TS4104": 1 + }, + "src/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion.tsx": { + "TS2345": 1 + }, + "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": { + "TS2339": 2 + }, + "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaEnvGroup.tsx": { + "TS2739": 1 + }, + "src/lib/combos/builderDraft.ts": { + "TS2741": 1 + }, + "src/lib/providers/codexFastTier.ts": { + "TS2367": 1 + }, + "src/lib/services/htmlRewriter.ts": { + "TS2322": 2, + "TS2345": 2 + }, + "src/mitm/inspector/sseMerger.ts": { + "TS2352": 1 + }, + "src/shared/components/Header.tsx": { + "TS2353": 1 + }, + "src/shared/components/MonacoEditor.tsx": { + "TS2307": 1 + }, + "src/shared/components/OAuthModal.tsx": { + "TS2769": 4, + "TS2345": 4 + }, + "src/shared/components/SkillsConceptCard.tsx": { + "TS2503": 1 + }, + "src/shared/components/analytics/charts.tsx": { + "TS2345": 1 + }, + "src/shared/components/analytics/rechartsDonuts.tsx": { + "TS2739": 2 + }, + "src/shared/hooks/useElectron.ts": { + "TS2339": 19 + }, + "src/shared/providers/webSessionCredentials.ts": { + "TS2353": 1, + "TS2322": 1 + }, + "src/shared/schemas/cliCatalog.ts": { + "TS2554": 2 + }, + "src/shared/services/opencodeConfig.ts": { + "TS2345": 1 + } +} diff --git a/config/quality/dependency-allowlist.json b/config/quality/dependency-allowlist.json index 6dae8b6274..5ecfb4329a 100644 --- a/config/quality/dependency-allowlist.json +++ b/config/quality/dependency-allowlist.json @@ -45,6 +45,7 @@ "concurrently", "cross-env", "csv-stringify", + "ctrf", "dompurify", "dpdm", "electron", @@ -63,6 +64,7 @@ "glob", "http-proxy-middleware", "https-proxy-agent", + "httpyac", "husky", "ink", "ink-spinner", @@ -74,6 +76,7 @@ "jscpd", "jsdom", "jsonc-parser", + "junit-to-ctrf", "keytar", "knip", "license-checker-rseidelsohn", @@ -99,7 +102,9 @@ "pino-abstract-transport", "pino-pretty", "playwright", + "playwright-ctrf-json-reporter", "prettier", + "promptfoo", "react", "react-dom", "react-is", diff --git a/config/quality/e2e-timings.json b/config/quality/e2e-timings.json new file mode 100644 index 0000000000..77571fcbf9 --- /dev/null +++ b/config/quality/e2e-timings.json @@ -0,0 +1,39 @@ +{ + "_meta": "Relative weights for scripts/quality/balance-e2e-shards.mjs (LPT shard packing). Unitless \u2014 only ratios matter. Seeded from spec LOC (proxy) on 2026-07-13; replace values with real per-file durations (seconds) from a full CI run's reports whenever convenient. New specs without an entry get the median weight.", + "a11y-resilience.spec.ts": 59, + "a11y.spec.ts": 281, + "agent-bridge-traffic-cross.spec.ts": 155, + "agent-bridge.spec.ts": 160, + "agent-skills-page.spec.ts": 205, + "analytics-tabs.spec.ts": 334, + "api-keys-flow.spec.ts": 643, + "api.spec.ts": 30, + "combo-live-studio.spec.ts": 35, + "combo-unification.spec.ts": 192, + "combos-flow.spec.ts": 629, + "compression-studio.spec.ts": 55, + "error-pages.spec.ts": 101, + "group-b-activity-feed.spec.ts": 96, + "group-b-quota-plans-config.spec.ts": 154, + "group-b-quota-share-pools.spec.ts": 98, + "group-b-redirect-logs-activity.spec.ts": 56, + "memory-engine.spec.ts": 607, + "memory-qdrant-routes.spec.ts": 360, + "memory-settings.spec.ts": 193, + "navigation.spec.ts": 28, + "playground-compare.spec.ts": 138, + "playground-studio.spec.ts": 101, + "protocol-visibility.spec.ts": 25, + "providers-bailian-coding-plan.spec.ts": 240, + "providers-management.spec.ts": 324, + "proxy-registry.smoke.spec.ts": 218, + "resilience-plan-alignment.spec.ts": 382, + "responsive.spec.ts": 21, + "search-tools-studio.spec.ts": 133, + "settings-toggles.spec.ts": 127, + "skills-marketplace.spec.ts": 209, + "smoke.spec.ts": 33, + "traffic-inspector.spec.ts": 212, + "translator-friendly.spec.ts": 115, + "visual-resilience-smoke.spec.ts": 20 +} \ No newline at end of file diff --git a/config/quality/quality-baseline.json b/config/quality/quality-baseline.json index f660fc33d3..3e8cd63fe6 100644 --- a/config/quality/quality-baseline.json +++ b/config/quality/quality-baseline.json @@ -27,22 +27,22 @@ "eps": 0 }, "coverage.statements": { - "value": 76.5, + "value": 80.8, "direction": "up", "tightenSlack": 5 }, "coverage.lines": { - "value": 76.5, + "value": 80.8, "direction": "up", "tightenSlack": 5 }, "coverage.functions": { - "value": 82, + "value": 86.44, "direction": "up", "tightenSlack": 5 }, "coverage.branches": { - "value": 73, + "value": 78.1, "direction": "up", "eps": 1.5, "tightenSlack": 5 @@ -54,49 +54,49 @@ "tightenSlack": 10 }, "coverage.combo.lines": { - "value": 80, + "value": 85.42, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.accountFallback.lines": { - "value": 88, + "value": 96.78, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.auth.lines": { - "value": 90, + "value": 92.55, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.routeGuard.lines": { - "value": 94, + "value": 98.73, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.error.lines": { - "value": 88, + "value": 92.13, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.publicCreds.lines": { - "value": 92, + "value": 99.07, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "coverage.circuitBreaker.lines": { - "value": 92, + "value": 95.09, "direction": "up", "eps": 1.5, "tightenSlack": 10 }, "openapiCoverage.pct": { - "value": 38.0, + "value": 38, "direction": "up", "eps": 0.5, "_tighten_2026_07_04_v3844_release": "36.9 -> 39.3 (aperto exigido pelo --require-tighten no PR de release #5925). A cobertura OpenAPI melhorou no ciclo (9 rotas documentadas em 8fb020676 + as rotas novas de #5939/#5817/#6034/#5998 documentadas junto das features). 39.3 = valor medido pelo CI Quality Ratchet no run 28708141003 (tip 00c55afcb).", @@ -158,7 +158,8 @@ "dedicatedGate": true }, "secretFindings": { - "value": 3, + "_note": "Zeroed 2026-07-13 (WS6/D3): the 3 frozen generic-api-key FPs are allowlisted with justification in .gitleaks.toml — any NEW finding regresses the ratchet.", + "value": 0, "direction": "down", "dedicatedGate": true }, @@ -187,12 +188,6 @@ "dedicatedGate": true, "_note": "oasdiff breaking-change gate (Fase 9 Onda 0). Blocks any breaking change vs base spec." }, - "semgrepFindings": { - "value": 0, - "direction": "down", - "dedicatedGate": true, - "_note": "semgrep owasp/secrets findings. ADVISORY until first CI value is frozen, then flip blocking (Fase 9)." - }, "mutationScore.src/sse/services/auth.ts": { "value": 52.57, "direction": "up", diff --git a/docs/architecture/QUALITY_GATES.md b/docs/architecture/QUALITY_GATES.md index 3686b9eef4..5fa94abd27 100644 --- a/docs/architecture/QUALITY_GATES.md +++ b/docs/architecture/QUALITY_GATES.md @@ -46,6 +46,7 @@ Runs on every PR to `main`. Blocks merge on failure. | `check:docs-sync` | CHANGELOG version, OpenAPI version, and `llm.txt` are in sync | Yes | | `typecheck:core` | TypeScript compilation without errors (advisory warnings only) | Yes | | `typecheck:noimplicit:core` | Strict `noImplicitAny` — forward-looking; many pre-existing call sites still need annotations | **Advisory** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` scoped to `src/app/(dashboard)/**` (#7033) — `typecheck:core`'s curated 27-file allowlist does not include any dashboard TSX, and `next build` never type-checks it either (`next.config.mjs` sets `ignoreBuildErrors: true`), so orphaned-identifier regressions there (#6625/#6909) were invisible to CI. Diffs against a frozen per-file/per-TS-code count baseline (`config/quality/dashboard-typecheck-baseline.json`, same stale-enforcement pattern as `check:known-symbols`) — only NEW errors beyond the baselined count fail the gate; ratchet down with `--update` when a pre-existing error is fixed. | Yes | ### Job: `quality-gate` @@ -173,6 +174,31 @@ pending implementation). --- +## Test Retry Policy (WS5.4, v3.8.49) + +Retry is per-runner, never a global blanket — a blanket retry converts real regressions +into invisible flakes: + +| Runner | Policy | Why | +| --- | --- | --- | +| Playwright (e2e) | `retries: 1` in CI only, with `trace: on-first-retry` | Browser/network timing is genuinely nondeterministic; one retry with a trace turns a flake into a diagnosable artifact | +| Vitest | NO global retry. A proven-flaky test gets an explicit per-test retry (visible in the diff, reviewed in PR) | Keeps the quarantine list in the repo, never opaque | +| node:test (unit) | NO retry, ever | A flaky unit test is a bug in the test — fix it, don't re-roll it | + +Target SLOs once flake telemetry lands (WS5.2/5.3): <1% flake rate per test +("fix now" threshold), ≥95% pass rate per pipeline. Industry reference values — +recalibrate against our own measurements. + +## Release-Level Ratchet Drift (WS5.5, v3.8.49) + +When a ratchet (file-size, complexity, eslint warnings) regresses on the PURE release +tip — i.e. the COMBINATION of merges regressed it, and no single PR reproduces the +regression on its own branch — the fix belongs to the **release captain, once, on the +release branch**: prefer extraction/refactor; rebaseline only with the documented +justification entry. Never push combination drift onto a contributor PR, and never +rebaseline per-PR (that hides real regressions). Discriminate first: reproduce the +red against the pure tip in a probe worktree before assuming your PR caused it. + ## Allowlist Policy Every gate that cannot fail on pre-existing violations uses a frozen allowlist diff --git a/docs/compression/COMPRESSION_GUIDE.md b/docs/compression/COMPRESSION_GUIDE.md index 472cf36b61..48bf546093 100644 --- a/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/compression/COMPRESSION_GUIDE.md @@ -278,7 +278,8 @@ Every compressed request includes stats in the server logs: | Phase 1 | Off, Lite | ✅ Shipped | | Phase 2 | Standard, Aggressive, Ultra | ✅ Shipped | | Phase 3 | RTK, Stacked, Compression Combos | ✅ Shipped | -| Phase 4 | Output Styles, SLM-tier Ultra, adaptive context-budget, eval harness | ✅ Shipped | +| Phase 4 | Output Styles, SLM-tier Ultra, eval harness | ✅ Shipped | +| Phase 4C | Adaptive context-budget ("dial") — compute engine + API (`contextBudget` on `PUT /api/settings/compression`) | ✅ Shipped (API-configurable; dashboard controls not yet built, #7005) | --- diff --git a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md index b97bf07d96..782d23d747 100644 --- a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md @@ -272,7 +272,7 @@ OmniRoute 提供两层防护:请求侧的注入扫描和响应侧的 PII 脱 | `OMNIROUTE_PUBLIC_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 用于组合异步回调 URL 的公共源。kie.ai 回调的最低优先级回退;也用作其他中继的通用公共 URL。 | | `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | Usage 页面使用的 CrofAI 配额查询端点。可覆盖为中继/测试固定件。 | | `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | Usage 页面使用的 OpenCode (zen/go) 配额查询端点。可覆盖为中继/测试固定件。 | -| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | `https://api.z.ai/api/monitor/usage/quota/limit` | `open-sse/services/usage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。可覆盖为中继/测试固定件。 | +| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(未设置)_ | `open-sse/services/opencodeOllamaUsage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。OpenCode Go 没有公开的配额 API,因此没有默认值;除非运维人员显式设置该变量选择接入自建/镜像端点,否则不会发起网络请求。 | | `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | 配置了 workspace ID 和 auth Cookie 时用于配额抓取的 OpenCode Go Dashboard 基础 URL。可覆盖为中继/测试固定件。 | | `OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go workspace ID。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | | `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID 环境变量的备选名,在较短的别名之前使用。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 | diff --git a/docs/ops/HOMOLOGATION.md b/docs/ops/HOMOLOGATION.md new file mode 100644 index 0000000000..2754d12956 --- /dev/null +++ b/docs/ops/HOMOLOGATION.md @@ -0,0 +1,104 @@ +--- +title: "Homologation Suite (npm run homolog)" +version: 3.8.49 +lastUpdated: 2026-07-14 +--- + +# Homologation Suite (`npm run homolog`) + +Real-environment E2E validation of the OmniRoute deploy running on the homologation VPS +(`HOMOLOG_BASE_URL`, e.g. `http://192.168.0.15:20128`). One command replaces the manual +release STOP #2 checklist with an automated, evidence-producing run. + +## What it covers + +| Layer | What it checks | Implementation | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| L0 — health/parity | `/api/monitoring/health` responds `200` with `status: "healthy"` and the expected version | `scripts/homolog/lib/parity.mjs` | +| L1a — ephemeral key | Admin login → `POST /api/keys` creates a scoped API key for the run, revoked (`DELETE /api/keys/:id`) in a `finally` block regardless of outcome | `scripts/homolog/lib/adminClient.mjs` | +| L1b — API surface | `/v1/models` catalog, a real non-streaming chat completion (tier-critical model, `max_tokens: 5`), an invalid-key `401`, and public `/api/monitoring/health` | `tests/homolog/api/core.http` (httpYac) | +| L1c — SSE streaming | Real streaming chat completion; asserts `text/event-stream`, at least one content delta, and a `[DONE]` terminator | `scripts/homolog/lib/sseCheck.mjs` | +| L2 — real providers | One minimal-cost chat request per critical provider present in the live `/v1/models` catalog, generated on the fly via promptfoo | `scripts/homolog/gen-promptfoo.mjs` + `scripts/homolog/lib/providerTiers.mjs` | +| L4a — UI auth | Logs in once via the real login form and reuses the session (`storageState`) across the UI layer | `tests/homolog/ui/auth.setup.ts` | +| L4b — UI routes | Every static `page.tsx` under `src/app/(dashboard)/dashboard` (discovered from the filesystem, dynamic `[param]` routes skipped) loads without an HTTP error, a page error, or the Next.js error boundary | `tests/homolog/ui/routes.spec.ts` | +| L4c — UI critical flow | Creates an API key through the dashboard UI and revokes it again (leaves no residue on the VPS) | `tests/homolog/ui/api-key-flow.spec.ts` | +| L5 — unified report | Merges httpYac (via `junit-to-ctrf`), the promptfoo→CTRF adapter, and the Playwright CTRF reporter into one `homolog-ctrf.json`, plus a human-readable `homolog-report/summary.md` | `scripts/homolog/run.mjs` | + +Zero LLM involvement in the replay itself — this is a deterministic regression battery, +not an eval. AI only enters in future maintenance work (see Roadmap below). + +## Prerequisites + +1. Copy `.env.homolog.example` to `.env.homolog` (gitignored — never commit it) and fill in: + - `HOMOLOG_BASE_URL` — the target deploy, e.g. `http://192.168.0.15:20128`. + - `HOMOLOG_ADMIN_PASSWORD` — the dashboard management password for that deploy. + - `HOMOLOG_CRITICAL_PROVIDERS` — comma-separated provider prefixes that get a real + smoke chat request (e.g. `openai,anthropic,gemini,codex,grok,glm,deepseek,openrouter`). + - `HOMOLOG_API_KEY` — leave empty in normal runs; the suite creates and revokes its + own ephemeral key. Only set this to debug a single layer in isolation. +2. `npm install` in the repo (the suite's dependencies — `httpyac`, `promptfoo`, + `playwright-ctrf-json-reporter`, `junit-to-ctrf`, `ctrf` — are regular devDependencies). +3. `npx playwright install` if the browser binaries are not already present. + +## How to run + +```bash +npm run homolog +``` + +To validate against a deploy whose version does not match the local `package.json` +(e.g. a homologation box still on a previous patch release), override the expected +version explicitly: + +```bash +HOMOLOG_EXPECT_VERSION=3.8.47 npm run homolog +``` + +The run exits non-zero if any layer fails, and always attempts to revoke the ephemeral +API key it created, even on failure (`finally` block in `scripts/homolog/run.mjs`). + +## Reading the report + +All output lands in `homolog-report/` (gitignored): + +- `summary.md` — the same table printed to stdout, one row per layer (✅/❌ + detail). +- `homolog-ctrf.json` — the unified CTRF report (merge of API/SSE, provider-smoke, and + UI results) — this is the artifact to attach to a release STOP #2 checklist. +- `httpyac-junit.xml`, `api-ctrf.json`, `providers-ctrf.json`, `ui-ctrf.json` — the + per-layer raw/intermediate reports. +- `promptfooconfig.yaml`, `provider-misses.json` — the generated promptfoo config for + the current run and any critical providers that were missing from the live catalog. + +A failing L0 aborts immediately (no ephemeral key is created) since a version/health +mismatch means every downstream layer would be validating the wrong deploy. + +## Re-baselining when the UI changes legitimately + +L4b (route smoke) and L4c (API-key UI flow) are driven by real DOM locators, not +snapshots, so most legitimate UI changes do not require any suite update. When a change +does break a locator (e.g. a renamed button label or a moved settings page): + +1. Re-confirm the locator against the current source (the specs already document which + file/line each locator was confirmed against — follow the same pattern, don't guess). +2. Update the spec in `tests/homolog/ui/`. +3. Re-run `npm run homolog` (or just the affected Playwright spec) against the VPS to + confirm the fix, then commit. + +There is no visual/pixel baseline in this suite (F1) — see Roadmap for that. + +## Roadmap (F2 / F3) + +Design and phased rollout live in the internal planning spec +`_tasks/superpowers/specs/2026-07-13-homolog-e2e-suite-design.md` (not linked — internal +`_tasks/` artifact, not part of this repo's tracked docs). Summary: + +- **F2** — full walkthrough recording → Playwright Test Agents (`planner`/`generator`) + turn it into flow specs (create combo, test provider, edit settings, MCP tools) + + visual regression baseline (Lost Pixel) with masks over dynamic data (metrics, + timestamps, logs) + a `healer` maintenance routine per release. +- **F3** — resilience/contract/wiring coverage: toxiproxy + a fake OpenAI-compatible + provider on the devbox, a `homolog-resilience` combo on the VPS pointed at it + (injected timeout → assert fallback + circuit breaker open/close via + `/api/monitoring/health`); gated Schemathesis contract testing against + `docs/openapi.yaml` (low `--max-examples`, fixed seeds, non-LLM endpoints only); and + wiring `npm run homolog` + its `summary.md` into the `/generate-release` STOP #2 phase. diff --git a/docs/ops/MERGE_TRAIN.md b/docs/ops/MERGE_TRAIN.md new file mode 100644 index 0000000000..d821191b63 --- /dev/null +++ b/docs/ops/MERGE_TRAIN.md @@ -0,0 +1,63 @@ +--- +title: Merge Queue & Manual Merge-Train Runbook +--- + +# Merge Queue & Manual Merge-Train Runbook + +Since v3.8.49 (WS3.2/WS3.4 of the quality/velocity plan) the default merge path for +reviewed PRs into `release/vX.Y.Z` is the **Mergify merge queue** (`.mergify.yml`); +the **manual merge-train** documented below is the FALLBACK — used during incidents, +release freezes, or if the Mergify Open Source plan ever changes. + +## Default path: the Mergify queue + +1. PR is reviewed/greened by the campaigns and approved by the owner's pre-merge ⭐ + gate (the report + per-item decision — see `/merge-prs` Step 0.75). +2. The owner (or the session acting on the owner's decision) applies the **`queue`** + label. The label IS the merge approval; Mergify only executes it. +3. Mergify batches up to 10 queued PRs, validates the batch against the fast-gates, + and merges (squash). A red batch is **bisected automatically** — the offending PR + is isolated in ~log2(N) revalidations and unqueued; the rest proceed. +4. Post-merge, the continuous release-green workflow validates the new tip on push + and opens an attribution issue if the combination regressed (never auto-revert). + +Guardrails (mirror `CLAUDE.md` Hard Rules #21/#22): + +- **Release freeze open** → do NOT label PRs targeting the frozen branch; retarget to + the active `release/vX+1` first. +- **Another session's in-flight PR** → never label it; only the owning session queues + its own work. +- Tests-only diffs and `hotfix`-labeled PRs already run reduced CI (see + `RELEASE_CHECKLIST.md` → Hotfix Fast-Lane); the queue conditions accept whatever + check set actually ran (`#check-failure=0` + `#check-pending=0`). + +## Fallback: the manual merge-train + +Used when the queue is unavailable. This codifies the practice that drained 33 PRs in +one day during the v3.8.47 cycle: + +1. **Assemble the batch** (~10–30 reviewed+approved PRs). Check `linked:` collisions + (same `tap.testFiles`, same CHANGELOG hunks) and serialize those. +2. **Validate ONCE**: in an isolated worktree off the release tip, merge all batch + heads locally, then run the release-equivalent suite + (`npm run check:release-green`, add `--with-build` before a release). +3. **Green** → merge the PRs in sequence (re-checking `state,headRefOid` before each — + a PR whose head moved re-enters review). Prove the net diff of each merge is the + PR's own change (no auto-resolve reverts: audit `git diff --stat` for + out-of-scope deletions). +4. **Red** → bisect the batch by halves (validate each half) instead of re-validating + one-by-one; drop the offending PR back to the review queue with the evidence. +5. **Never**: merge during a freeze into the frozen branch; `git stash` anywhere; + blanket-rerun CI hoping a red goes away (rule: a red is information). + +## Tiering (why the queue is safe with fast-gates only) + +- **Per PR** (quality.yml fast-gates): TIA-impacted tests + full unit 4-shard + + vitest + lint bag + typecheck + docs/changelog integrity. +- **Per batch/tip** (continuous release-green): `--quick` HARD gates on every push to + the release branch; full `--with-build --full-ci` sweeps 3×/day. +- **Per release** (ci.yml on the release PR): the complete matrix incl. E2E ×9, + package-artifact + tarball boot-smoke, coverage/ratchets. + +Nothing is validated less than before — the heavy surface just runs per batch/tip +instead of per PR, which is what removes the O(N) round-trips. diff --git a/docs/ops/RELEASE_CHECKLIST.md b/docs/ops/RELEASE_CHECKLIST.md index 6a0bb0d8bf..4d298c66fd 100644 --- a/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/ops/RELEASE_CHECKLIST.md @@ -37,6 +37,56 @@ npm run test:e2e # optional but recommended /capture-release-evidences-cc ``` +## npm Staged Publishing (default since v3.8.49 — WS1.3/D2) + +The npm-publish workflow no longer publishes directly: it boots the packed tarball +(`check:pack-boot`) and then runs `npm stage publish` — the exact bytes are parked on +the registry, **not installable** until the owner approves. The human 2FA gate moved +to AFTER the proof, not before it. + +**Owner flow after the workflow goes green:** + +1. `npm stage list omniroute` — find the stage id (also printed in the workflow summary). +2. Verify the staged bytes (recommended): `npm stage download `, then install the + downloaded tarball into a temp prefix and boot it (`npm run check:pack-boot` automates + the same pack→install→boot verdict in CI). +3. `npm stage approve ` — the 2FA prompt IS the publish. `npm stage reject ` discards. +4. Post-publish net: the post-publish verifier (WS1.4 of the v3.8.49 plan) installs the + published version from the public registry in a clean container and boots it. + +**Emergency fallback:** `workflow_dispatch` with `publish_mode=direct` restores the +legacy immediate `npm publish` (use only if staging itself misbehaves; record why). + +**One-time hardening (owner, npmjs.com):** configure the Trusted Publisher for +`omniroute` in stage-only mode so a leaked long-lived token cannot `npm publish` +directly from anywhere — CI can only stage; only the owner's 2FA releases. + +**Broken-artifact playbook (unchanged):** `npm deprecate omniroute@ " — use "` +as the default reflex (minutes, reversible); `npm unpublish` only inside the 72h/no-dependents +window and never as the first move. Docker: never rewrite a version tag — rollback is +repointing `latest` to the last good digest. +## Hotfix Fast-Lane (label `hotfix`) + +A PR labeled `hotfix` skips the heavy CI matrix (9-shard E2E, coverage ratchet, +quality-gate, quality-extended) and keeps the fast, high-signal gates: build, +unit shards, integration, vitest, lint/typecheck, docs-sync, `check:pack-artifact` +and the tarball boot-smoke (`check:pack-boot`). Target: green in ≤15min instead of ~33min. + +**Entry policy — all four required (modeled on Chromium/VS Code/Node emergency lanes):** + +1. **Severity**: production is broken — a published artifact crashes on boot / a + security fix / every user of the release is affected. "Important" is not "broken". +2. **Authority**: only the repository owner applies the `hotfix` label. The label IS + the approval — never self-serve on a campaign PR. +3. **Evidence**: the PR body links the previous fully-green heavy run (the suite the + skipped jobs would re-validate) plus the fix's own failing-then-passing test. +4. **Scope**: cherry-pick-only — the minimal fix, no refactors, no ride-alongs. + +The skipped coverage/ratchet surface is re-validated by the next full run on the +release branch (continuous release-green) — the lane skips WAITING, never validation. +Tests-only diffs (all files under `tests/`, none under `tests/e2e/`) skip the E2E +matrix automatically, without any label. + ## Detailed Checklist ### Pre-release diff --git a/docs/ops/RUNNER_BOX.md b/docs/ops/RUNNER_BOX.md new file mode 100644 index 0000000000..07bd70cb04 --- /dev/null +++ b/docs/ops/RUNNER_BOX.md @@ -0,0 +1,35 @@ +--- +title: Self-Hosted Runner Box Operations +--- + +# Self-Hosted Runner Box Operations (.113 pool) + +The self-hosted pool (`self-hosted, omni-release` labels) runs on the 16 GB box at +`192.168.0.113`. Two failure modes recurred on release days and were, until v3.8.49, +manual discipline; the **janitor script codifies them** (WS3.3 of the quality plan): + +1. **Orphaned temp/work dirs** filling the disk → disk-full SQLite errors mid-job. +2. **>4 concurrent runners** → OOM-killed jobs (8-wide killed jobs twice on the + v3.8.47 release day; 4-wide is the proven ceiling). + +## Install the janitor (one-time, on the box) + +```bash +sudo mkdir -p /opt/omniroute-ops +sudo cp scripts/ops/runner-janitor.sh /opt/omniroute-ops/ +sudo chmod +x /opt/omniroute-ops/runner-janitor.sh +( sudo crontab -l 2>/dev/null; echo '*/30 * * * * /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1' ) | sudo crontab - +``` + +What it does every 30min: sweeps runner temp leftovers older than 24h, alerts at +≥85% root-disk usage, and alerts when more than the runner ceiling (default 4, tunable +via the script's own environment) of `Runner.Listener` processes are up. Alerts land in `/var/log/runner-janitor.log` +with a non-zero exit (grep for `⚠`). + +## Operating rules + +- **Ceiling: 4 runners** on the 16 GB box. Runners 5–8 stay STOPPED except for + explicit off-peak experiments — never during a release window. +- Stopping a runner mid-job cancels the job (observed live): `systemctl stop` + only when its runner is idle (`Runner.Listener` without a `Runner.Worker` child). +- The `.15` VPS is homologation-only — never runs CI runners. diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index bd8fe06946..7e9b2b0773 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -280,7 +280,7 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp | `OMNIROUTE_PUBLIC_URL` | _(unset)_ | `open-sse/utils/kieTask.ts` | Public origin used to compose async callback URLs. Lowest-priority fallback for kie.ai callbacks; also used as a generic public URL for other relays. | | `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | CrofAI quota lookup endpoint used by the Usage page. Override for relays / test fixtures. | | `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | OpenCode (zen/go) quota lookup endpoint used by the Usage page. Override for relays / test fixtures. | -| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | `https://api.z.ai/api/monitor/usage/quota/limit` | `open-sse/services/usage.ts` | OpenCode Go quota lookup endpoint used by the Usage page. Override for relays / test fixtures. | +| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(unset)_ | `open-sse/services/opencodeOllamaUsage.ts` | OpenCode Go quota lookup endpoint used by the Usage page. OpenCode Go has no public quota API, so this has no default and the network call is skipped unless the operator opts in to a self-hosted/mirrored endpoint. | | `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | OpenCode Go dashboard base URL used for quota scraping when a workspace ID and auth cookie are configured. Override for relays / test fixtures. | | `OPENCODE_GO_WORKSPACE_ID` | _(unset)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID used for dashboard quota scraping. Prefer the per-connection Dashboard field when multiple accounts are configured. | | `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(unset)_ | `open-sse/services/usage.ts` | Alternate OpenCode Go workspace ID env var used before the shorter alias. Prefer the per-connection Dashboard field when multiple accounts are configured. | diff --git a/next.config.mjs b/next.config.mjs index ac7197503c..b3f7887bb5 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -128,11 +128,25 @@ const nextConfig = { // expected diagnostic — suppress it here rather than fight the analyzer, // mirroring the isNextIntlExtractorDynamicImportWarning precedent below // for the webpack path. (#6582) + // open-sse/services/compression/ruleLoader.ts and + // .../engines/rtk/filterLoader.ts both define an identical + // getModuleDir() helper that walks up directories via + // path.resolve(anchor) + fs.existsSync(...) in a loop with a + // non-literal argument — the same dynamic-path fs access pattern as + // the agentSkills case above, but not covered by that narrower + // allowlist glob, so the "Overly broad patterns..." warning kept + // firing (610 times, once per entry point transitively importing the + // compression module). Same known-benign, bounded fs access; + // suppressed here rather than fought. (#7051, follow-up to #6582) ignoreIssue: [ { path: "**/src/lib/agentSkills/**", description: /Overly broad patterns can lead to build performance issues/, }, + { + path: "**/open-sse/services/compression/**", + description: /Overly broad patterns can lead to build performance issues/, + }, ], }, output: "standalone", diff --git a/open-sse/config/providers/registry/opencode/index.ts b/open-sse/config/providers/registry/opencode/index.ts index 5f3779d72a..4aaa6ea046 100644 --- a/open-sse/config/providers/registry/opencode/index.ts +++ b/open-sse/config/providers/registry/opencode/index.ts @@ -23,29 +23,15 @@ export const opencodeProvider: RegistryEntry = { interleavedField: "reasoning_content", }, { id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true }, - // #3110: MiniMax M3 free tier via OpenCode - // #3328: MiniMax M3 is multimodal (verified: describes base64 images via the - // opencode upstream) — flag it so vision requests aren't gated/stripped. - { - id: "minimax-m3-free", - name: "MiniMax M3 Free", - contextLength: 1048576, - supportsVision: true, - }, - { id: "minimax-m2.5-free", name: "MiniMax M2.5 Free", contextLength: 204800 }, - { id: "ling-2.6-1t-free", name: "Ling 2.6 Free", contextLength: 262000 }, - { - id: "trinity-large-preview-free", - name: "Trinity Large Preview Free", - contextLength: 131000, - }, - { id: "nemotron-3-super-free", name: "Nemotron 3 Super Free", contextLength: 1000000 }, - { - id: "qwen3.6-plus-free", - name: "Qwen3.6 Plus Free", - targetFormat: "claude", - supportsVision: false, - contextLength: 200000, - }, + // #6998: 2026-07-14 refresh — the upstream free tier rotated its lineup; + // minimax-m3-free, minimax-m2.5-free, ling-2.6-1t-free, + // trinity-large-preview-free, nemotron-3-super-free and qwen3.6-plus-free + // were delisted (401 "Model X is not supported") and replaced by the 4 + // entries below, confirmed live against + // https://opencode.ai/zen/v1/chat/completions. + { id: "mimo-v2.5-free", name: "MiMo V2.5 Free", contextLength: 131000 }, + { id: "hy3-free", name: "HY3 Free", contextLength: 131000 }, + { id: "nemotron-3-ultra-free", name: "Nemotron 3 Ultra Free", contextLength: 1000000 }, + { id: "north-mini-code-free", name: "North Mini Code Free", contextLength: 131000 }, ], }; diff --git a/open-sse/executors/duckduckgo-web.ts b/open-sse/executors/duckduckgo-web.ts index e9c6053548..bf653932c7 100644 --- a/open-sse/executors/duckduckgo-web.ts +++ b/open-sse/executors/duckduckgo-web.ts @@ -64,11 +64,18 @@ function shouldUseBrowserBacked(): boolean { interface DuckDuckGoVqdHeaders { vqd4: string | null; vqdHash1: string | null; + // #6996: the real upstream HTTP status of the VQD-acquisition attempt (null when + // no request was made / a network error was thrown). Lets execute() distinguish a + // retryable 429 rate-limit from a genuine 5xx instead of collapsing both to 503. + status: number | null; + retryAfter: string | null; } interface DuckDuckGoAuthHeaders { vqd4: string | null; vqdHash1: string | null; + status: number | null; + retryAfter: string | null; } interface DuckDuckGoModelCapabilities { @@ -369,10 +376,13 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { const isStreaming = stream !== false; const upstreamHeaders = upstreamExtraHeaders || {}; - const errorResponse = (status: number, message: string): Response => + const errorResponse = (status: number, message: string, retryAfter?: string | null): Response => new Response(JSON.stringify({ error: { message } }), { status, - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + ...(retryAfter ? { "Retry-After": retryAfter } : {}), + }, }); if (messages.length === 0) { @@ -468,6 +478,19 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { const vqdHeaders = await this.acquireAuthHeaders(mergedSignal); if (!vqdHeaders.vqd4 && !vqdHeaders.vqdHash1) { clearTimeout(timeout); + // #6996: surface the real upstream status instead of a hardcoded 503 so a + // 429 rate-limit gets a connection-cooldown, not a whole-provider circuit + // breaker trip (see CLAUDE.md "Provider Circuit Breaker" — only + // 408/500/502/503/504 should trip it, not 429). Any other non-2xx status + // (403 anti-bot challenge, genuine 5xx, or a thrown network error where + // status is null) keeps the existing 503 fallback. + if (vqdHeaders.status === 429) { + return errorResponse( + 429, + "Failed to acquire VQD token: upstream rate limited", + vqdHeaders.retryAfter + ); + } return errorResponse(503, "Failed to acquire VQD token"); } @@ -555,16 +578,25 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { }); this.rememberResponseCookies(resp); - if (!resp.ok) return { vqd4: null, vqdHash1: null }; + if (!resp.ok) { + return { + vqd4: null, + vqdHash1: null, + status: resp.status, + retryAfter: resp.headers.get("Retry-After"), + }; + } return { vqd4: resp.headers.get("x-vqd-4"), vqdHash1: resp.headers.get("x-vqd-hash-1"), + status: resp.status, + retryAfter: null, }; } catch (error) { if (error instanceof DOMException && error.name === "AbortError") { throw error; } - return { vqd4: null, vqdHash1: null }; + return { vqd4: null, vqdHash1: null, status: null, retryAfter: null }; } } @@ -576,6 +608,8 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { return { vqd4: null, vqdHash1: await solveDuckDuckGoChallenge(challenge, FAKE_HEADERS["User-Agent"]), + status: null, + retryAfter: null, }; } catch (error) { void error; @@ -588,6 +622,8 @@ export class DuckDuckGoWebExecutor extends BaseExecutor { return { vqd4: headers.vqd4, vqdHash1: await solveDuckDuckGoChallenge(headers.vqdHash1, FAKE_HEADERS["User-Agent"]), + status: headers.status, + retryAfter: headers.retryAfter, }; } catch (error) { void error; diff --git a/open-sse/executors/gemini-web.ts b/open-sse/executors/gemini-web.ts index 48a4cbbfdb..0286cc8202 100644 --- a/open-sse/executors/gemini-web.ts +++ b/open-sse/executors/gemini-web.ts @@ -112,12 +112,15 @@ function parseCookies(raw: string): Array<{ name: string; value: string }> { * [["wrb.fr", null, ""]] * * The JSON string contains nested array: inner[4][0][1] = ["text chunks"]. - * We concatenate text from every wrb.fr line because Gemini can split one - * assistant answer across multiple StreamGenerate chunks. + * Each wrb.fr line is a CUMULATIVE snapshot of the whole answer generated so + * far (not an independent delta), so we keep only the text from the LAST + * frame that yields non-empty text instead of concatenating every frame — + * concatenating would reproduce the same growing text with each snapshot + * (see #7163). */ export function parseStreamResponse(raw: string): string { const lines = raw.split("\n"); - const textChunks: string[] = []; + let lastText = ""; for (const rawLine of lines) { const line = rawLine.trim(); @@ -133,12 +136,12 @@ export function parseStreamResponse(raw: string): string { const responseArray = inner?.[4]?.[0]?.[1]; if (!Array.isArray(responseArray)) continue; const text = responseArray.filter((c: unknown) => typeof c === "string").join(""); - if (text) textChunks.push(text); + if (text) lastText = text; } catch { // Skip unparseable lines } } - return textChunks.join(""); + return lastText; } function readCredentialString(value: unknown): string { diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts index 039e7fa637..68168fd8cf 100644 --- a/open-sse/executors/index.ts +++ b/open-sse/executors/index.ts @@ -170,8 +170,26 @@ const executors = { const defaultCache = new Map(); +// #6699 — providers that exist ONLY as Cloud Agent task-API entries +// (CLOUD_AGENT_PROVIDERS / staticModels "Available Models" catalog) and have no +// chat-completions REGISTRY entry anywhere in open-sse/. Without this guard, +// getExecutor() silently falls through to DefaultExecutor's +// `PROVIDERS[provider] || PROVIDERS.openai` fallback, sending the user's real +// provider key to OpenAI's endpoint (mislabeled as coming from the provider the +// user actually selected). Starting with just "jules" (the reported case); +// "devin" and "codex-cloud" share the same structural gap and are left for a +// follow-up once their own chat-routing behavior is confirmed. +const CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS = new Set(["jules"]); + export function getExecutor(provider) { if (executors[provider]) return executors[provider]; + if (CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS.has(provider)) { + const err = new Error( + `Provider "${provider}" is a cloud-agent provider and does not support direct chat completions; use the Cloud Agents task API instead.` + ); + (err as Error & { status?: number }).status = 400; + throw err; + } if (!defaultCache.has(provider)) defaultCache.set(provider, new DefaultExecutor(provider)); return defaultCache.get(provider); } diff --git a/open-sse/executors/mimocode.ts b/open-sse/executors/mimocode.ts index 1313a1d2ab..115e5a4bad 100644 --- a/open-sse/executors/mimocode.ts +++ b/open-sse/executors/mimocode.ts @@ -90,6 +90,7 @@ export interface AccountProxyConfig { port: number; username?: string; password?: string; + relayAuth?: string; } | null; } diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 60fe0f7951..2453199b8f 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -21,6 +21,7 @@ export interface OpencodeAccountProxyConfig { port: number; username?: string; password?: string; + relayAuth?: string; } | null; } diff --git a/open-sse/executors/yuanbao-web.ts b/open-sse/executors/yuanbao-web.ts index 20ef5662e0..75a3d2c615 100644 --- a/open-sse/executors/yuanbao-web.ts +++ b/open-sse/executors/yuanbao-web.ts @@ -106,8 +106,13 @@ function buildPrompt(messages: Array>): string { /** Build the `hy_source=web; hy_user=...; hy_token=...` cookie from the pasted header. */ function buildYuanbaoCookie(rawApiKey: string): { cookie: string; hasToken: boolean } { const raw = stripCookieInputPrefix(rawApiKey || ""); - const hyUser = extractCookieValue(raw, "hy_user"); - const hyToken = extractCookieValue(raw, "hy_token"); + // Guard the extractCookieValue bare-value fallback: for input that is a single + // FOREIGN pair (e.g. "some_other=abc") the helper returns the whole string, which + // used to fool this validation into forwarding garbage upstream (the request only + // failed when Tencent replied 401 — a live-network dependency). Yuanbao needs the + // two distinct cookies, so only trust an extraction the input explicitly names. + const hyUser = raw.includes("hy_user=") ? extractCookieValue(raw, "hy_user") : null; + const hyToken = raw.includes("hy_token=") ? extractCookieValue(raw, "hy_token") : null; if (hyUser && hyToken) { return { cookie: `hy_source=web; hy_user=${hyUser}; hy_token=${hyToken}`, hasToken: true }; diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 275ecbab41..32562fc526 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -41,6 +41,7 @@ import { isSubscriptionQuotaText, buildSubscriptionQuotaFallback, buildWeeklyQuotaFallback, + buildSessionQuotaFallback, } from "./quotaTextCooldowns.ts"; import { parseDayGranularityResetMs, shouldPreserveQuotaSignals } from "./quotaResetParsing.ts"; @@ -1454,6 +1455,12 @@ export function checkFallbackError( } const weeklyResult = buildWeeklyQuotaFallback(errorStr); if (weeklyResult) return weeklyResult; + // Issue #7071 (session usage cap) is the same sibling gap as #3709 above — + // runs UNCONDITIONALLY for the same reason: apikey-category providers + // like ollama-cloud are excluded from the oauth-only shouldUseQuotaSignal + // gate. + const sessionResult = buildSessionQuotaFallback(errorStr); + if (sessionResult) return sessionResult; const quotaResetHintMs = parseRetryFromErrorText(errorStr); if ( diff --git a/open-sse/services/claudeTlsClient.ts b/open-sse/services/claudeTlsClient.ts index 499c32e130..0e785af9f5 100644 --- a/open-sse/services/claudeTlsClient.ts +++ b/open-sse/services/claudeTlsClient.ts @@ -344,7 +344,19 @@ function toHeaders(raw: Record): Headers { // to a file path, terminating when the upstream sends `streamOutputEOFSymbol`. // We tail the file from a worker and surface the bytes as a ReadableStream. -async function tlsFetchStreaming( +// Cap for the bounded fallback read of a non-SSE error body straight from the +// streaming temp file (mirrors the 2048-byte cap executors/claude-web.ts +// already applies when reading error bodies) — avoids buffering an unbounded +// error page into memory. See #7134. +const MAX_ERROR_BODY_BYTES = 16 * 1024; + +/** + * Exported for tests (issue #7134): allows injecting a fake `client` so the + * non-SSE error-body fallback path can be exercised without + * `--experimental-test-module-mocks`, matching the DI pattern already used + * by `__setTlsFetchOverrideForTesting` for the outer `tlsFetchClaude`. + */ +export async function tlsFetchStreaming( client: { request: (url: string, opts: Record) => Promise }, url: string, requestOptions: Record, @@ -417,11 +429,22 @@ async function tlsFetchStreaming( const r = await requestPromise.catch( (e) => ({ status: 502, headers: {}, body: String(e) }) as TlsResponseLike ); + // tls-client-node's `streamOutputPath` mode writes the response body to + // the temp file chunk-by-chunk and does NOT also populate the resolved + // response's in-memory `body` field (confirmed against + // node_modules/tls-client-node/dist/response.js) — so for every non-SSE, + // non-2xx claude-web response (400/403/429/500 with a real JSON/HTML + // error), `r.body` is empty even though the real bytes are sitting in + // `path` (we just peeked them above). Prefer `r.body` when it IS + // populated (some native-client modes do fill it in); otherwise fall + // back to a bounded read of the temp file so the real upstream error + // detail reaches the caller instead of being silently discarded. #7134 + const text = r.body || (await readFirstBytes(path, MAX_ERROR_BODY_BYTES).catch(() => "")); await cleanupTempPath(path); return { status: r.status, headers: toHeaders(r.headers), - text: r.body, + text, body: null, }; } diff --git a/open-sse/services/opencodeOllamaUsage.ts b/open-sse/services/opencodeOllamaUsage.ts index 25cd3c3cc9..b3beb4606d 100644 --- a/open-sse/services/opencodeOllamaUsage.ts +++ b/open-sse/services/opencodeOllamaUsage.ts @@ -13,8 +13,13 @@ type UsageQuota = { currency?: string; }; -const OPENCODE_GO_QUOTA_URL = - process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL ?? "https://api.z.ai/api/monitor/usage/quota/limit"; +// OpenCode Go does not expose a public quota API. There is no working +// opencode.ai endpoint to default to (see #7022) — the quota-by-API-key path +// below is opt-in only and activates exclusively when the operator sets +// OMNIROUTE_OPENCODE_GO_QUOTA_URL explicitly. Never hardcode a third-party +// host here (a previous default silently sent the user's API key to an +// unrelated Z.AI endpoint). +const OPENCODE_GO_QUOTA_URL = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL?.trim() || ""; const OPENCODE_GO_DASHBOARD_BASE_URL = process.env.OMNIROUTE_OPENCODE_GO_DASHBOARD_URL ?? "https://opencode.ai/workspace"; const OPENCODE_GO_QUOTA_TOTALS = { session: 12, weekly: 30, mcp_monthly: 60 } as const; @@ -335,6 +340,15 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?: }; } + if (!OPENCODE_GO_QUOTA_URL) { + return { + message: + "OpenCode Go does not expose a public quota API. " + + "Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping, " + + "or set OMNIROUTE_OPENCODE_GO_QUOTA_URL to opt in to an explicit quota endpoint.", + }; + } + try { const res = await fetch(OPENCODE_GO_QUOTA_URL, { headers: { @@ -348,7 +362,8 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?: if (res.status === 401 || res.status === 403) { return { message: - "OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " + + "OpenCode Go API key is valid for chat/models but cannot read quota from the configured " + + "OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " + "Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping.", }; } @@ -374,7 +389,8 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?: ) { return { message: - "OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " + + "OpenCode Go API key is valid for chat/models but cannot read quota from the configured " + + "OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " + "Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping.", }; } diff --git a/open-sse/services/quotaTextCooldowns.ts b/open-sse/services/quotaTextCooldowns.ts index 0146d72ea5..4f481b822d 100644 --- a/open-sse/services/quotaTextCooldowns.ts +++ b/open-sse/services/quotaTextCooldowns.ts @@ -103,3 +103,37 @@ export function buildWeeklyQuotaFallback(errorStr: string): QuotaTextFallback | reason: RateLimitReason.QUOTA_EXHAUSTED, }; } + +// ─── Issue #7071 — Ollama Cloud 5-hour SESSION usage cap ─────────────────── +// +// Ollama Cloud also enforces a rolling 5-hour "session" usage cap, sibling to +// the weekly cap above (#3709/#6638). On cap the upstream returns 429 with a +// body like "you () have reached your session usage limit". Same +// root cause as the weekly gap: neither the generic subscription-quota-text +// classifier nor the weekly one recognize "session" wording, so the account +// fell through to the generic 429 backoff and got retried within the same +// 5-hour window instead of cooling down for it — combo/LKGP routing cycled +// back to the "exhausted" account instead of advancing to the next one. +// +// Patterns are scoped to "session ... usage limit" / "session limit reached" +// / "reached your session ... usage limit" phrasing (not a bare "session" +// match) so unrelated "session expired"/"session token invalid" auth errors +// from other providers are not misclassified as quota-exhausted. +const SESSION_QUOTA_COOLDOWN_MS = 5 * 60 * 60 * 1000; // 5 hours + +export function isSessionUsageLimitText(lower: string): boolean { + return ( + lower.includes("session usage limit") || + lower.includes("session limit reached") || + (lower.includes("reached your session") && lower.includes("usage limit")) + ); +} + +export function buildSessionQuotaFallback(errorStr: string): QuotaTextFallback | null { + if (!isSessionUsageLimitText(errorStr.toLowerCase())) return null; + return { + shouldFallback: true, + cooldownMs: SESSION_QUOTA_COOLDOWN_MS, + reason: RateLimitReason.QUOTA_EXHAUSTED, + }; +} diff --git a/open-sse/translator/response/openai-responses.ts b/open-sse/translator/response/openai-responses.ts index 42fca56462..fdd214d06d 100644 --- a/open-sse/translator/response/openai-responses.ts +++ b/open-sse/translator/response/openai-responses.ts @@ -12,7 +12,7 @@ import { stripEmptyOptionalToolArgs, normalizeOutputIndex, normalizeUpstreamFailure, - extractResponsesReasoningSummaryText, + getVisibleResponsesReasoningSummaryText, } from "./openai-responses/pureHelpers.ts"; import { createEventEmitter } from "./openai-responses/eventEmitter.ts"; @@ -1070,7 +1070,11 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) { !(state.reasoningItemsWithDelta instanceof Set && state.reasoningItemsWithDelta.size > 0); if (emittedForItem || emittedWithoutItemId) return null; - const summaryText = extractResponsesReasoningSummaryText(item); + // #7095/#7176 reconciliation: computed WITHOUT mutating `item`, so an + // encrypted-only reasoning item (and its `encrypted_content`) is never + // rewritten with a fabricated `summary` — the placeholder only feeds this + // synthetic client-facing delta chunk. + const summaryText = getVisibleResponsesReasoningSummaryText(item); if (!summaryText) return null; return buildResponsesReasoningDeltaChunk(state, summaryText); } diff --git a/open-sse/translator/response/openai-responses/pureHelpers.ts b/open-sse/translator/response/openai-responses/pureHelpers.ts index 094d7dc58d..50e9cfe6c6 100644 --- a/open-sse/translator/response/openai-responses/pureHelpers.ts +++ b/open-sse/translator/response/openai-responses/pureHelpers.ts @@ -163,3 +163,28 @@ export function extractResponsesReasoningSummaryText(item) { ) .join(""); } + +// #7095/#7176 — when Codex exposes a reasoning item only as encrypted private +// reasoning (no plaintext summary), chat clients would otherwise see nothing in +// their thinking panel. Reconciles two goals that used to be in tension: +// - #7095 wants a visible placeholder in the chat client. +// - #7176 wants the upstream response item left untouched, so `encrypted_content` +// (needed by Codex for subsequent requests) is never overwritten by a +// fabricated `summary`. +// This function computes the placeholder text WITHOUT mutating `item` — callers +// use the returned text for synthetic client-facing events only. +const ENCRYPTED_REASONING_PLACEHOLDER = + "Codex is reasoning, but the upstream Responses API exposed this reasoning block only as encrypted private reasoning. OmniRoute cannot recover the plaintext."; + +export function getVisibleResponsesReasoningSummaryText(item) { + const existingSummary = extractResponsesReasoningSummaryText(item); + if (existingSummary) return existingSummary; + + const hasEncryptedReasoning = + item && + item.type === "reasoning" && + typeof item.encrypted_content === "string" && + item.encrypted_content.length > 0; + + return hasEncryptedReasoning ? ENCRYPTED_REASONING_PLACEHOLDER : ""; +} diff --git a/open-sse/utils/error.ts b/open-sse/utils/error.ts index 88efa6c67d..be3de82dda 100644 --- a/open-sse/utils/error.ts +++ b/open-sse/utils/error.ts @@ -145,6 +145,22 @@ function clampDiagStr(v: unknown, max = 128): string { return typeof v === "string" ? v.slice(0, max).replace(/[\r\n]+/g, " ") : ""; } +/** + * HTTP header values must be Latin1/ByteString (undici throws a TypeError + * otherwise — see #6612). Replace any codepoint outside the Latin1 range + * (0-255) with "?" so header construction never throws. Only used for the + * literal header value; the JSON body keeps the original, unsanitized + * readable text via `sanitizeComboDiagnostics`. + */ +function toHeaderSafeAscii(v: string): string { + let out = ""; + for (let i = 0; i < v.length; i++) { + const code = v.charCodeAt(i); + out += code > 255 ? "?" : v[i]; + } + return out; +} + /** * Whitelist projection — guarantees only id/reason string primitives + integer * counts can escape, regardless of what the caller assembled. This is the secret @@ -186,10 +202,12 @@ export function errorResponseWithComboDiagnostics( if (opts.code) body.error.code = opts.code; if (opts.type) body.error.type = opts.type; body.diagnostics = safe; - const excludedHeader = safe.excluded - .map((e) => `${e.provider}${e.model ? `/${e.model}` : ""}:${e.reason}`) - .join(",") - .slice(0, 900); + const excludedHeader = toHeaderSafeAscii( + safe.excluded + .map((e) => `${e.provider}${e.model ? `/${e.model}` : ""}:${e.reason}`) + .join(",") + .slice(0, 900) + ); return new Response(JSON.stringify(body), { status: statusCode, headers: { @@ -197,7 +215,7 @@ export function errorResponseWithComboDiagnostics( "x-omniroute-combo-pool-size": String(safe.poolSize), "x-omniroute-combo-attempted": String(safe.attempted), "x-omniroute-combo-excluded": excludedHeader, - "x-omniroute-combo-terminal-reason": safe.terminalReason.slice(0, 200), + "x-omniroute-combo-terminal-reason": toHeaderSafeAscii(safe.terminalReason.slice(0, 200)), }, }); } diff --git a/open-sse/utils/passthroughTailProcessor.ts b/open-sse/utils/passthroughTailProcessor.ts index 82b57da8aa..845bc6e352 100644 --- a/open-sse/utils/passthroughTailProcessor.ts +++ b/open-sse/utils/passthroughTailProcessor.ts @@ -37,7 +37,6 @@ export type PassthroughTailProcessorContext = { appendPassthroughReasoning: (value: string) => void; getResponsesReasoningKey: (payload: Record) => string | null; markResponsesReasoningSummarySeen: (key: string) => void; - ensureVisibleResponsesReasoningSummary: (payload: Record) => boolean; emitSyntheticResponsesReasoningSummary: (payload: Record) => void; passthroughResponsesOutputItems: unknown[]; passthroughResponsesPendingFunctionCalls: Map; @@ -136,12 +135,8 @@ function handleResponsesTailPayload( } } if (parsed.type === "response.output_item.done" && parsed.item) { - const reasoningSummaryInjected = context.ensureVisibleResponsesReasoningSummary(parsed); context.emitSyntheticResponsesReasoningSummary(parsed); pushUniqueResponsesOutputItems(context.passthroughResponsesOutputItems, [parsed.item]); - if (reasoningSummaryInjected) { - output = `data: ${JSON.stringify(parsed)}\n\n`; - } const item = asRecord(parsed.item); if (item.type === "function_call") { const pendingKey = getFunctionCallPendingKey(item); diff --git a/open-sse/utils/stream.ts b/open-sse/utils/stream.ts index 581f1f6342..974d532e9e 100644 --- a/open-sse/utils/stream.ts +++ b/open-sse/utils/stream.ts @@ -52,6 +52,7 @@ import { stripResponsesLifecycleEcho, } from "./responsesStreamHelpers.ts"; import { processBufferedPassthroughLine } from "./passthroughTailProcessor.ts"; +import { getVisibleResponsesReasoningSummaryText } from "../translator/response/openai-responses/pureHelpers.ts"; import { getAnyReasoningValue, getReadableReasoningValue, @@ -1006,49 +1007,6 @@ export function createSSEStream(options: StreamOptions = {}) { return responseId !== null && outputIndex !== null ? `${responseId}:${outputIndex}` : null; }; - const getResponsesReasoningSummaryText = (item: Record): string => { - return Array.isArray(item.summary) - ? item.summary - .map((part) => { - if (!part || typeof part !== "object" || Array.isArray(part)) { - return ""; - } - return typeof (part as Record).text === "string" - ? ((part as Record).text as string) - : ""; - }) - .join("") - : ""; - }; - - const ensureVisibleResponsesReasoningSummary = (payload: Record): boolean => { - const item = - payload.item && typeof payload.item === "object" && !Array.isArray(payload.item) - ? (payload.item as Record) - : null; - if (!item || item.type !== "reasoning") { - return false; - } - - if (getResponsesReasoningSummaryText(item)) { - return false; - } - - const hasEncryptedReasoning = - typeof item.encrypted_content === "string" && item.encrypted_content.length > 0; - if (!hasEncryptedReasoning) { - return false; - } - - item.summary = [ - { - type: "summary_text", - text: "Codex is reasoning, but the upstream Responses API exposed this reasoning block only as encrypted state. OmniRoute cannot recover the private reasoning text.", - }, - ]; - return true; - }; - const emitSyntheticResponsesReasoningSummary = ( controller: TransformStreamDefaultController, payload: Record @@ -1061,8 +1019,10 @@ export function createSSEStream(options: StreamOptions = {}) { return; } - ensureVisibleResponsesReasoningSummary(payload); - const visibleSummary = getResponsesReasoningSummaryText(item); + // #7095/#7176 reconciliation: compute the visible placeholder WITHOUT + // mutating `item` — the encrypted reasoning item (and its `encrypted_content`, + // required by Codex for subsequent requests) is forwarded to the client intact. + const visibleSummary = getVisibleResponsesReasoningSummaryText(item); if (!visibleSummary) { return; @@ -1485,13 +1445,8 @@ export function createSSEStream(options: StreamOptions = {}) { // response.completed snapshot can be backfilled when upstream // returns an empty `output` (happens with store: false). if (parsed.type === "response.output_item.done" && parsed.item) { - const reasoningSummaryInjected = ensureVisibleResponsesReasoningSummary(parsed); emitSyntheticResponsesReasoningSummary(controller, parsed); pushUniqueResponsesOutputItems(passthroughResponsesOutputItems, [parsed.item]); - if (reasoningSummaryInjected) { - output = `data: ${JSON.stringify(parsed)}\n\n`; - injectedUsage = true; - } if (parsed.item?.type === "function_call") { const pendingKey = typeof parsed.item.id === "string" @@ -2181,7 +2136,6 @@ export function createSSEStream(options: StreamOptions = {}) { markResponsesReasoningSummarySeen: (key: string) => { passthroughResponsesReasoningSummarySeen.add(key); }, - ensureVisibleResponsesReasoningSummary, emitSyntheticResponsesReasoningSummary: (payload: Record) => emitSyntheticResponsesReasoningSummary(controller, payload), passthroughResponsesOutputItems, diff --git a/package-lock.json b/package-lock.json index 63c1f5fc55..a678e41868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,21 +114,26 @@ "c8": "^11.0.0", "concurrently": "^10.0.3", "cross-env": "^10.1.0", + "ctrf": "^0.2.1", "dpdm": "^4.2.0", "eslint": "^9.39.4", "eslint-config-next": "16.2.10", "eslint-plugin-sonarjs": "^4.1.0", "fast-check": "^4.8.0", "glob": "^13.0.6", + "httpyac": "^6.16.7", "husky": "^9.1.7", "jscpd": "^4.2.5", "jsdom": "^29.1.1", + "junit-to-ctrf": "^0.0.14", "knip": "^6.18.0", "license-checker-rseidelsohn": "^5.0.1", "lint-staged": "^17.0.8", "lockfile-lint": "^5.0.0", "node-loader": "^2.1.0", + "playwright-ctrf-json-reporter": "^0.0.29", "prettier": "^3.8.3", + "promptfoo": "^0.121.18", "size-limit": "^12.1.0", "tailwindcss": "^4.3.0", "type-coverage": "^2.29.7", @@ -159,6 +164,66 @@ "dev": true, "license": "MIT" }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.149", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.149.tgz", + "integrity": "sha512-+EVPEHqdJVJn0FZHBd6NyH4rvlTK7X79B6xFuW5bfZIP1G/7Y5OTEgxpL0hjOCAxDBG4ZFM6SZWnVBXxeR1x8w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.14", + "@ai-sdk/provider-utils": "4.0.38", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.14.tgz", + "integrity": "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.38", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.38.tgz", + "integrity": "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.14", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-zen/node-fetch-event-source": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@ai-zen/node-fetch-event-source/-/node-fetch-event-source-2.1.4.tgz", + "integrity": "sha512-OHFwPJecr+qwlyX5CGmTvKAKPZAdZaxvx/XDqS1lx4I2ZAk9riU0XnEaRGOOAEFrdcLZ98O5yWqubwjaQc0umg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cross-fetch": "^4.0.0" + } + }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", @@ -229,6 +294,217 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@anthropic-ai/claude-agent-sdk": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.195.tgz", + "integrity": "sha512-FVmXu9pvOMbuBKWrF8YsYQdQ/upOpv5rS8lFAnFO5jbyXT/2hN7kEPd2vd2GJpaMvNcO/KptyQUK5AxjjTz3+w==", + "dev": true, + "license": "SEE LICENSE IN README.md", + "optional": true, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.195", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.195", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.195", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.195", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.195", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.195", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.195", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.195" + }, + "peerDependencies": { + "@anthropic-ai/sdk": ">=0.93.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.0.0" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.195.tgz", + "integrity": "sha512-WIMM/8HRCLsTDHFTIwQvvE8WCA/oaMJtdQxsP7iNyfzIGwXbuOyU95V8vYIhZfaO2yaSpbBRncunq4CtR5H4ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.195.tgz", + "integrity": "sha512-RY7DB+4LXosE0MJ+XELmakfPrDN1YX4lkk9CTDm28jGCVcESRz9kAEqbyaiC48dZcmN9V1NCLutzINGdcr1TBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.195.tgz", + "integrity": "sha512-JuIq5Fnz/F1snl0aqi1gcuRZqPWoPNrL9dJ0DuievCxKkO8hnEz/Mmn5Zos7x1X8HE//ZnEvmQXoEQEZXonJew==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.195.tgz", + "integrity": "sha512-ZmyBA/AFzhgutcxb7dbhCm6GTjJytwNYXTxJoKE2B3A409WCYccjMqeji6vCMNxyyfylglGo5D8dVMIxW9aoug==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.195.tgz", + "integrity": "sha512-s1lNi1cL93luoqsItH+fNO4KpIhdkvnVhWGGQUQ/8ftwa2gfmcIQnOg1hG8Ks+KzeD3UUQ8L9YEVHVADnFI/9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.195.tgz", + "integrity": "sha512-nf8Q/LauB+ZOC6QDjxNhbsvwUtYjKYnaWJLTYFwhkmsLujePnety1AtT/1ubaUoq5AM1j297DhMlYTasa79OUA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.195.tgz", + "integrity": "sha512-hbkDE+xPIZzRWm+D+BKrH9uJH6USIZdDIlsyrIlGi3JFHoieYoA1vdUNyldSS9+F3ZqQtfPjr2Qy08IVB6akYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { + "version": "0.3.195", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.195.tgz", + "integrity": "sha512-av0piEB3X1Dzhpr8A+DqHVZ9y8s1jpn8enzwX0TKKUPBn5IqLTWC7wD6v66aoUgu4f+g4ThZirmDZA6shyPEZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.106.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.106.0.tgz", + "integrity": "sha512-ufwVvYNDBj2dzOGupBCTaNzBLxqcTnGOzI4z8Wouxlt+mT3J3HuOmatgCy1VmwCHOUueqZ41ERhm0O99OUcbWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.5.0.tgz", + "integrity": "sha512-Ps4w0FwrDoeVK6hfYxWkVbkmxm+zN+6xoXF2ZfEhfiox0ZNbcSAiUWO6iAIvP5bc3DB270r+EaKcoT1IUyzfxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^4.2.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", @@ -295,6 +571,45 @@ "js-tiktoken": "*" } }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.16.tgz", + "integrity": "sha512-EKnvkXSmz3IpA99tCNuI+dLFXyZyClSm8zns9sB/elvkU+MTuomAs6toJMPMBf98/fICG/urXDkzGz0/c3yyAQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-agent-runtime": { + "version": "3.1086.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent-runtime/-/client-bedrock-agent-runtime-3.1086.0.tgz", + "integrity": "sha512-urlsx3VdXU+FP0vs88bTNDbqtv6DKoDTkINo5Bts3apIeN6JW0pamjF9PmyJsenTLQvbHB6lkTHicar5CEp9og==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-node": "^3.972.67", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-bedrock-runtime": { "version": "3.1081.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1081.0.tgz", @@ -318,18 +633,63 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1086.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1086.0.tgz", + "integrity": "sha512-6+7mVMPKetZmmF2L1yRJ+rN9b1OwVgc5sju2mj8ixdxuGjtVZ0ekFlcWGBFMQT9gpFk55PPLBng/XRYO3qah5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/checksums": "^3.1000.16", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-node": "^3.972.67", + "@aws-sdk/middleware-sdk-s3": "^3.972.62", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker-runtime": { + "version": "3.1086.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker-runtime/-/client-sagemaker-runtime-3.1086.0.tgz", + "integrity": "sha512-E37Ros1JssB5+hGtUz9yhjyxcZ+vH6rxentCXMerXqBfh/wnQTDxR4LESLwruXeDwfMupzySZRwvj8y+kBaSXA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-node": "^3.972.67", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/core": { - "version": "3.974.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.29.tgz", - "integrity": "sha512-yqKcltLbtRh1ubzhRSldIs8jFHNZlyMlgoIccCC0aDVbrB99nXaBdmfr89mK7obWX/NVg4rAMpCpZ6dCDiVBtA==", + "version": "3.975.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", + "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.15", - "@aws-sdk/xml-builder": "^3.972.33", + "@aws-sdk/types": "^3.974.0", + "@aws-sdk/xml-builder": "^3.972.34", "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.29.0", - "@smithy/signature-v4": "^5.6.1", - "@smithy/types": "^4.15.1", + "@smithy/core": "^3.29.2", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -338,15 +698,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.55", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.55.tgz", - "integrity": "sha512-Ah36tYkqyaVnaHkx7VseoTYrHUmwgBps3V+wnrC1idhIIMGlviH0FtrX9EIPdAlVHvXC7FQZLhmHBRz+pLaiWg==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", + "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -354,17 +714,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.57.tgz", - "integrity": "sha512-/vp6i5YEliJqRm5k/BDmYjAyRAMTdkjW6UciVRk9oh/0OfDCWeb/ih7hqte4lFvKXkIbsqe9AdK9LQK6NGardw==", + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", + "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/fetch-http-handler": "^5.6.2", - "@smithy/node-http-handler": "^4.9.2", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -372,23 +732,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.62", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.62.tgz", - "integrity": "sha512-pQIRiQQs+MUlVnJdWJ7/6KS0WxcLRVfut57OFgwC3cnM1F8mXw3Kh4gAVwj6AtvD6CWx8x6+po4ENRcqe64XrQ==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", + "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/credential-provider-env": "^3.972.55", - "@aws-sdk/credential-provider-http": "^3.972.57", - "@aws-sdk/credential-provider-login": "^3.972.61", - "@aws-sdk/credential-provider-process": "^3.972.55", - "@aws-sdk/credential-provider-sso": "^3.972.61", - "@aws-sdk/credential-provider-web-identity": "^3.972.61", - "@aws-sdk/nested-clients": "^3.997.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/credential-provider-imds": "^4.4.5", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-login": "^3.972.63", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -396,16 +756,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.61", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.61.tgz", - "integrity": "sha512-jtrxWwC7slqxh7DnAWHrwsA3UwCsnlypdYtavGT7EX5p791wxWQys7QzkCZ7JvOMAyylDtPoxyV+ic0zg3rV9g==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", + "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/nested-clients": "^3.997.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -413,21 +773,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.64", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.64.tgz", - "integrity": "sha512-zyKVYDyMR9VQL/kPi03ygN2vtD9uLMuWRLoJ77KxgZZaS1VlJloI+SzleF9Zg4HWUI+AIu+ZRs8zsJFNqbrxsw==", + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.67.tgz", + "integrity": "sha512-oYlzWst56rlhhjbYnexwv5hVLYe1cW4liLObhDfxDLI4RAQzleMVHQgQgx7XsC4HKj4e3kjT8v9DId+Pi/dndw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.55", - "@aws-sdk/credential-provider-http": "^3.972.57", - "@aws-sdk/credential-provider-ini": "^3.972.62", - "@aws-sdk/credential-provider-process": "^3.972.55", - "@aws-sdk/credential-provider-sso": "^3.972.61", - "@aws-sdk/credential-provider-web-identity": "^3.972.61", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/credential-provider-imds": "^4.4.5", - "@smithy/types": "^4.15.1", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-ini": "^3.973.1", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -435,15 +795,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.55", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.55.tgz", - "integrity": "sha512-x0XjjF0l1WGRtK2vEhTZqCguQuAIZLep9l2+eeEmuxQQjjD3BlGQXY5xADR+l3t576UX+dxRkRtTjEu40l81Vw==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", + "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -451,17 +811,34 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.61", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.61.tgz", - "integrity": "sha512-d/V0VRsz73i+PHhbult/tx0Y1+de1SNQVsXkcQCmpfeBq7uODy/RTxNsOLpT9ZVHxcRNzbQFuywLKC33fUMIxA==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", + "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/nested-clients": "^3.997.29", - "@aws-sdk/token-providers": "3.1081.0", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/token-providers": "3.1083.0", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1083.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", + "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -469,16 +846,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.61", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.61.tgz", - "integrity": "sha512-Bv4n3NOI6hPy+rmr6Bw9R6LnBVRkcp3ncj2E2IKSYJG+0UkysSitWMvbgndNvMxDw7gE1pQ/ErwkNceuKwj7zQ==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", + "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/nested-clients": "^3.997.29", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -515,6 +892,25 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.62", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.62.tgz", + "integrity": "sha512-k8JJwYXVYlOOjWnPZDThQS1xDFJgi5Dokt73qFlDtrZAbdcint5aIdjB9XgJAAQVP5OoqcefQmh1FYXiPpvsvw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-websocket": { "version": "3.972.37", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.37.tgz", @@ -534,18 +930,18 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.29.tgz", - "integrity": "sha512-ot6v8J5W8P0w6ryyuIkXP1bHZHTlvwtn83mVCYaBE0GJ6tJX4vPSBx7M98w9O4wmmDruFsDBUMjhEHA+OosUFQ==", + "version": "3.997.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", + "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.29", - "@aws-sdk/signature-v4-multi-region": "^3.996.38", - "@aws-sdk/types": "^3.973.15", - "@smithy/core": "^3.29.0", - "@smithy/fetch-http-handler": "^5.6.2", - "@smithy/node-http-handler": "^4.9.2", - "@smithy/types": "^4.15.1", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -553,14 +949,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.38.tgz", - "integrity": "sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g==", + "version": "3.996.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", + "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.15", - "@smithy/signature-v4": "^5.6.1", - "@smithy/types": "^4.15.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -585,12 +981,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.15.tgz", - "integrity": "sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", + "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.15.1", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -598,12 +994,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.33", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.33.tgz", - "integrity": "sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A==", + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", + "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.15.1", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -632,6 +1028,451 @@ "playwright-core": ">= 1.0.0" } }, + "node_modules/@azure-rest/core-client": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.8.0.tgz", + "integrity": "sha512-F1ybHeN+++QhyFCF/ehLUEvrOB6fehPdFBFtGdj0C3B2lpQ9zkPiO5JDgsqc6IfjuUe6b3dAbXK0a7+VgSGfhw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.24.0", + "@azure/core-tracing": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/ai-projects": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@azure/ai-projects/-/ai-projects-2.3.1.tgz", + "integrity": "sha512-xrBy4UQqx5nhV4xx2dFdEMWFPSiwDHHDYKBkKXNTMn5SXyBujvjxBDU3w9c4sH9KDeMxSaGUP1dbNfMcgL04fQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure-rest/core-client": "^2.1.0", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.6.0", + "@azure/core-lro": "^3.1.0", + "@azure/core-paging": "^1.5.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-sse": "^2.1.3", + "@azure/core-util": "^1.9.0", + "@azure/identity": "^4.13.0", + "@azure/logger": "^1.1.4", + "@azure/storage-blob": "^12.26.0", + "@opentelemetry/api": "^1.9.0", + "openai": "^6.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", + "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.0.tgz", + "integrity": "sha512-JjQWO6akOck45PH/XBrxzsQGAiKrfFl4m5iggJ0ItMIz5omRufOXWpqCPpdjKN3vKDzlSUvFjaMb7Zwf0gvAdA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.5.0.tgz", + "integrity": "sha512-BoSmXPx2er1Ai+wKlDvj29jIQespCNBwEmKyZVHO2kEFsWbGjAjwMCGzug3DJM5/QYIV3vej0S1zcU5bq9fa8w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@azure/core-client": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-3.4.0.tgz", + "integrity": "sha512-y0uqcVFp5NHd7tkZcn8Nes6yIhVR05m4dd+L8foWiH1IsS75Z2BodJxwdErEF3bV+NSh6nkNnwPyXaLp0ma1Nw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.7.0.tgz", + "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", + "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-sse": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-sse/-/core-sse-2.4.0.tgz", + "integrity": "sha512-BFNVsoYE843I/q5/OFNHpaYN8TK8W99OwU9ipToYXdwB14o92A16ZjD6JW/BZ8kO7fWSM4jK2EoojAQmXAOExw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", + "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", + "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.6.0.tgz", + "integrity": "sha512-e7lX/dk//F6Qf7BB6PTY4+p2yuOQtyOeHGyapYHNwqSp2OnYpwQt49A/Nin2XmKBQ69pwagR4k/lQBq8lbHQkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-xml-parser": "^5.5.9", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/identity/node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/logger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", + "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.17.0.tgz", + "integrity": "sha512-/yTnW2TCk9Mh+2b/NOaHAN+MryUNxzRTaJD/YtrqOA9bpBWfTXn/iyReRbaLrK/btBo3stEzLyEvuWp2NZ5DuA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/msal-common": "16.11.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.11.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.11.1.tgz", + "integrity": "sha512-yPohvMwWLv1XnaWnIUyKUh8CvcVChCGqG/VluGwfGmaAfrZTNt5yQ+sIs462Sgw6+e2K83KGmMJ860p73ZSCrw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.4.0.tgz", + "integrity": "sha512-6EZEParwHRlnSSIikw8FNAnAzwmh71uhveUXdPNFeZFviJ9SH+rwFiurhjzXqICYTrpm3E+dj693QOwfPbJXAQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/msal-common": "16.11.1", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure/openai-assistants": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@azure/openai-assistants/-/openai-assistants-1.0.0-beta.6.tgz", + "integrity": "sha512-gINKKcqTpR0neF+36Owe0Q1u1JO3IK6clBzWTfZ+9V/TkQq+LoUgp5F8dKvSv/YChfwEpZA2r1DWCwNE07eYIQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure-rest/core-client": "^1.1.4", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.7.3", + "@azure/core-rest-pipeline": "^1.13.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.4", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/openai-assistants/node_modules/@azure-rest/core-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-1.4.0.tgz", + "integrity": "sha512-ozTDPBVUDR5eOnMIwhggbnVmOrka4fXCs8n8mvUo4WLLc38kki6bAOByDoVZZPz/pZy2jMt2kwfpvy/UjALj6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.33.0.tgz", + "integrity": "sha512-2SX8oP8PyblUcAFZSg39c8Ls+tFjavM6sBeV+qpw33mRzRhI/5hrFJmJ/x0H9xx5l6ECPvgSP8uPxqTeVbHNIA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.3", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.1", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/core-xml": "^1.4.5", + "@azure/logger": "^1.1.4", + "@azure/storage-common": "^12.4.1", + "events": "^3.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/storage-blob/node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-common": { + "version": "12.4.1", + "resolved": "https://registry.npmjs.org/@azure/storage-common/-/storage-common-12.4.1.tgz", + "integrity": "sha512-t14unw/WofGDUi7TKJrsyXyPsN+NLgRm7hMaq0llxNmTIzt7f257+6LE6FKIJPh88zLj6M7LPvzve0fEYg/L3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-rest-pipeline": "^1.24.0", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.1.4", + "events": "^3.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -1086,9 +1927,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1149,6 +1990,18 @@ "node": ">=18" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -1168,12 +2021,43 @@ "specificity": "bin/cli.js" } }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@chevrotain/types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", "license": "Apache-2.0" }, + "node_modules/@cloudamqp/amqp-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@cloudamqp/amqp-client/-/amqp-client-2.1.1.tgz", + "integrity": "sha512-u4nOBfpBTEiohQ/ThJVLmHx+G7mUt1aTXsLqhGVZAnTnA1AZpq/ja1Lhx7equRuE48AhQKS/UDQpV+7grLEF4Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1416,6 +2300,18 @@ "node": ">=20" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -2300,6 +3196,33 @@ } } }, + "node_modules/@fal-ai/client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@fal-ai/client/-/client-1.10.1.tgz", + "integrity": "sha512-c3AVeH31OioiI2J1BfW8Cryi1DhUYldnY3X35nv6xLMq3fU2NQOo+eYaR5mL2O8MoHHh+HzXdQuIyanIyeq+ug==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@msgpack/msgpack": "^3.0.0-beta2", + "eventsource-parser": "^1.1.2", + "robot3": "^0.4.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@fal-ai/client/node_modules/eventsource-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz", + "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.18" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -2408,6 +3331,86 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@googleapis/sheets": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@googleapis/sheets/-/sheets-13.0.2.tgz", + "integrity": "sha512-b1tBlMcfvNEziM4DZCikLOc9iqSlgCK1e5bMKtNQIADRXr1CQmbkHV3ZBVvTsFsjLErgihqO58Itn/kzCnSZ0A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@hapi/address": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", @@ -2484,6 +3487,14 @@ "node": ">=18" } }, + "node_modules/@huggingface/tokenizers": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz", + "integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, "node_modules/@huggingface/transformers": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.5.2.tgz", @@ -2549,6 +3560,45 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@ibm-cloud/watsonx-ai": { + "version": "1.7.15", + "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.7.15.tgz", + "integrity": "sha512-JUxFuACcKhDC+shavgyLEFjWiSsBjI+tjIBvLrzcHywh0HqTT0m+whe2kL3isPheBzrduSMfovwypbSuKwCp+g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "form-data": "^4.0.4", + "ibm-cloud-sdk-core": "^5.4.20" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@ibm-generative-ai/node-sdk": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@ibm-generative-ai/node-sdk/-/node-sdk-3.2.4.tgz", + "integrity": "sha512-HvJSYql3lOPYZcGb23mBw0kcWLlCX+n7EDRgJQxz7gIzx9WafUuDyl1IlTCXGfxolm0EhNIub79u9v7owtks0w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@ai-zen/node-fetch-event-source": "^2.1.2", + "fetch-retry": "^5.0.6", + "http-status-codes": "^2.3.0", + "openapi-fetch": "^0.8.2", + "p-queue-compat": "1.0.225", + "yaml": "^2.3.3" + }, + "peerDependencies": { + "@langchain/core": ">=0.1.0" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + } + } + }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -2620,6 +3670,54 @@ "@img/sharp-libvips-darwin-x64": "1.2.4" } }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-freebsd-wasm32/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-freebsd-wasm32/node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", @@ -2975,6 +4073,54 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32/node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-win32-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", @@ -3388,7 +4534,6 @@ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", - "optional": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3407,7 +4552,6 @@ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=12" }, @@ -3420,8 +4564,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", @@ -3429,7 +4572,6 @@ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3448,7 +4590,6 @@ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -3537,6 +4678,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jscpd/badge-reporter": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.2.5.tgz", @@ -3645,6 +4797,202 @@ "spark-md5": "^3.0.2" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@libsql/client": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.4.tgz", + "integrity": "sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.17.4", + "@libsql/hrana-client": "^0.10.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.28", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.17.4.tgz", + "integrity": "sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", + "integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", + "integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.10.0.tgz", + "integrity": "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", + "integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", + "integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", + "integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", + "integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", + "integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", + "integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", + "integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@lobehub/icons": { "version": "5.10.1", "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.1.tgz", @@ -3784,6 +5132,239 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.12.tgz", + "integrity": "sha512-QAfAMwNgnYxZ2C6D1HgeP7Gc4i/uvJRim415PCIL9ptRxWMNbWeLBYb2/9R4pGKny/s1FVu2JA2cxCUBUOggrA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", + "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "dev": true, + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -3803,6 +5384,13 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "dev": true, + "license": "MIT" + }, "node_modules/@next/env": { "version": "16.2.10", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.10.tgz", @@ -4188,6 +5776,19 @@ "node": ">= 10" } }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4838,6 +6439,631 @@ "node": ">=20.0" } }, + "node_modules/@openai/agents": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.11.8.tgz", + "integrity": "sha512-D4XHF2g+Ub/L9fRJT/xpuiCqHyxiKzZbi0BqQxnso42t+J049O/OSvVzFBcRskF4uPFAvs0TOOB7KBbanCwaYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@openai/agents-core": "0.11.8", + "@openai/agents-openai": "0.11.8", + "@openai/agents-realtime": "0.11.8", + "debug": "^4.4.0", + "openai": "^6.35.0" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@openai/agents-core": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.11.8.tgz", + "integrity": "sha512-TrE34RXXPoWYv2PjXf5hq3Eq+uvRJMNiY+Q5WBgEPjAg60yt2hya8cS2I8qkO6i25MjNJl37a25X0vL/gs5Wdg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.0", + "openai": "^6.35.0" + }, + "optionalDependencies": { + "@modelcontextprotocol/sdk": "^1.26.0" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@openai/agents-openai": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.11.8.tgz", + "integrity": "sha512-XjHCnJPGapgZBlh8y5oxU7zV0hrAQTF5im6HpUwaPcH5CeRFLtc06VXLso0vJ5G3g9e/J5gIh3S1iAxiJqEAVQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@openai/agents-core": "0.11.8", + "debug": "^4.4.0", + "openai": "^6.35.0" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@openai/agents-realtime": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.11.8.tgz", + "integrity": "sha512-i1qEGUE8GTW0neWgAc1aj/3wZFtstz8bVG2BvVbU/BzQbyhZV8j3CvndkMJGFfgeobvVmn2qGTV5Ry6ibfuxeQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@openai/agents-core": "0.11.8", + "@types/ws": "^8.18.1", + "debug": "^4.4.0", + "ws": "^8.18.1" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@openai/codex": { + "version": "0.142.5", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5.tgz", + "integrity": "sha512-WQEpD7l3k68eIAP0aq28EdR18ENBAf8DyprzFhzNwCOQJSv4nHzpwT8Fl30IJacprko2ZCmUBZjM2u941l2yLw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "codex": "bin/codex.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@openai/codex-darwin-arm64": "npm:@openai/codex@0.142.5-darwin-arm64", + "@openai/codex-darwin-x64": "npm:@openai/codex@0.142.5-darwin-x64", + "@openai/codex-linux-arm64": "npm:@openai/codex@0.142.5-linux-arm64", + "@openai/codex-linux-x64": "npm:@openai/codex@0.142.5-linux-x64", + "@openai/codex-win32-arm64": "npm:@openai/codex@0.142.5-win32-arm64", + "@openai/codex-win32-x64": "npm:@openai/codex@0.142.5-win32-x64" + } + }, + "node_modules/@openai/codex-darwin-arm64": { + "name": "@openai/codex", + "version": "0.142.5-darwin-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-darwin-arm64.tgz", + "integrity": "sha512-l43p8xv+Z/2/b6fCUc7/FmcQZsaPB7RFizLponGwHAnFOWe3i9Vky69p+up3BUam9AetoQQUv7Mo+2KdaFEqhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-darwin-x64": { + "name": "@openai/codex", + "version": "0.142.5-darwin-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-darwin-x64.tgz", + "integrity": "sha512-yk6A06/VmW7NFsa48OVPaj//g/zeSpd79wjuqfXZwW8ZKRYQm3+wCd3hWjPl79F3QnXvDvM2j3JMIBL3m3GXXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-arm64": { + "name": "@openai/codex", + "version": "0.142.5-linux-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-linux-arm64.tgz", + "integrity": "sha512-77ka5PSnm5HdxdBT99IwntCasmbqevlS0eiC0AtEb6ZXCLkim2gm0AWm+jNYy0EhbssvNK+KghayWo34HMgXeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-x64": { + "name": "@openai/codex", + "version": "0.142.5-linux-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-linux-x64.tgz", + "integrity": "sha512-pxY+d3NgNE57Y/MApD3/TZUAygxJN6I9h3ZeDUwe67mxWjUxsuapxMRFTKSznCalYbRAeZp752+AAXmUbmguEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-sdk": { + "version": "0.142.5", + "resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.142.5.tgz", + "integrity": "sha512-MConZ+eoBoZmkc4reezuzOgLtoI1BQBzo/nVYsSjtAIBpwKcgeEm1rfmqfUnTfFaBNHFTxBntcS7ZeQYuDPbWA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@openai/codex": "0.142.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@openai/codex-win32-arm64": { + "name": "@openai/codex", + "version": "0.142.5-win32-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-win32-arm64.tgz", + "integrity": "sha512-65BEqGbUZ7r0ayunIHdBjo5crwgbwKX/6puOcO+VCswUw/dXvDsN2IGcbXB52+bS9U5+FxP783cUHfTT6m40DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-win32-x64": { + "name": "@openai/codex", + "version": "0.142.5-win32-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.142.5-win32-x64.tgz", + "integrity": "sha512-a+wI4PEx9a2fg6V5ueTTDkOkr1XpEvA5RFXIbo/L2hOfzMmGtyRnbG24bCGu5Q2RSgVxSQV0aLkdb3vdYMNH9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.17.20", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.20.tgz", + "integrity": "sha512-Urb7Tp4mvJmyckNI/N79uEpWzZOyn4lE5LVPDVidXZ8BIlw6kxx2ctTPRYPJDF8HLPWGCZ7pCpHcmWgQCYyiNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz", + "integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.9.0.tgz", + "integrity": "sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.219.0.tgz", + "integrity": "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-exporter-base": "0.219.0", + "@opentelemetry/otlp-transformer": "0.219.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz", + "integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-transformer": "0.219.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz", + "integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-logs": "0.219.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.9.0.tgz", + "integrity": "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz", + "integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.9.0.tgz", + "integrity": "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.9.0.tgz", + "integrity": "sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/sdk-trace": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.9.0.tgz", + "integrity": "sha512-ec9a7ps37huy5itYk0MalaZdSLlM6AXWp/FhtEjgMpp5leEGojBDvAl/UWttQnkMZOvFHKzRESn8TD3yKTF5nQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.9.0", + "@opentelemetry/core": "2.9.0", + "@opentelemetry/sdk-trace-base": "2.9.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace/node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@orama/orama": { "version": "3.1.18", "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.18.tgz", @@ -6266,6 +8492,21 @@ "node": ">=14" } }, + "node_modules/@playwright/browser-chromium": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.61.1.tgz", + "integrity": "sha512-t3/zE0i9gik5R/NpRs7G2Xo/6NPeABW6ReplGdtkeWeAkaV764CgFgoKjJo21D2xgjnvDvRYubqBUu4xl0VCqA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "playwright-core": "1.61.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@playwright/test": { "version": "1.61.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", @@ -6323,40 +8564,50 @@ "node": ">=12" } }, + "node_modules/@posthog/core": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.23.1.tgz", + "integrity": "sha512-GViD5mOv/mcbZcyzz3z9CS0R79JzxVaqEz4sP5Dsea178M/j3ZWe6gaHDZB9yuyGfcmIMQ/8K14yv+7QrK4sQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "devOptional": true, "license": "BSD-3-Clause", - "optional": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1" } @@ -6365,29 +8616,29 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause", - "optional": true + "devOptional": true, + "license": "BSD-3-Clause" }, "node_modules/@radix-ui/number": { "version": "1.1.2", @@ -7146,6 +9397,99 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/@redis/bloom": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.12.1.tgz", + "integrity": "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/client/node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@redis/json": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.12.1.tgz", + "integrity": "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/search": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.12.1.tgz", + "integrity": "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.12.1.tgz", + "integrity": "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", @@ -7436,6 +9780,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -7636,6 +9997,23 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -7674,13 +10052,77 @@ "size-limit": "12.1.0" } }, + "node_modules/@slack/logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", + "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": ">=18" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.22.0.tgz", + "integrity": "sha512-sZ9lIgJhPX2qft/tKWiklFlc0o1FWeI7QtciZJfW1+ErH1eGGHvOZ8e73sleTCFEFJp1q/R0WeS8Oa7AsiDprg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.19.0.tgz", + "integrity": "sha512-ItjyjEZml+LDH8CjcCLRLJHh7VZtevPKExrRN3l5KWyBliyDnGAeoO4Y+K+fFBmRpKLYVPgqWMX4THldv2HVtA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/types": "^2.21.0", + "@types/node": ">=18", + "@types/retry": "0.12.0", + "axios": "^1.16.0", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/web-api/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/@smithy/core": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.1.tgz", - "integrity": "sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A==", + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz", + "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.15.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -7688,13 +10130,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.6.tgz", - "integrity": "sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz", + "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.1", - "@smithy/types": "^4.15.1", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -7702,13 +10144,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.3.tgz", - "integrity": "sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.5.tgz", + "integrity": "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.1", - "@smithy/types": "^4.15.1", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -7716,13 +10158,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.3.tgz", - "integrity": "sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.1", - "@smithy/types": "^4.15.1", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -7730,13 +10172,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.2.tgz", - "integrity": "sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.4.tgz", + "integrity": "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.29.1", - "@smithy/types": "^4.15.1", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -7744,9 +10186,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.15.1.tgz", - "integrity": "sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -7755,6 +10197,31 @@ "node": ">=18.0.0" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -8074,14 +10541,14 @@ "license": "Apache-2.0" }, "node_modules/@swc/core": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.21.tgz", - "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.27" }, "engines": { "node": ">=10" @@ -8091,18 +10558,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.21", - "@swc/core-darwin-x64": "1.15.21", - "@swc/core-linux-arm-gnueabihf": "1.15.21", - "@swc/core-linux-arm64-gnu": "1.15.21", - "@swc/core-linux-arm64-musl": "1.15.21", - "@swc/core-linux-ppc64-gnu": "1.15.21", - "@swc/core-linux-s390x-gnu": "1.15.21", - "@swc/core-linux-x64-gnu": "1.15.21", - "@swc/core-linux-x64-musl": "1.15.21", - "@swc/core-win32-arm64-msvc": "1.15.21", - "@swc/core-win32-ia32-msvc": "1.15.21", - "@swc/core-win32-x64-msvc": "1.15.21" + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -8114,9 +10581,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.21.tgz", - "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", "cpu": [ "arm64" ], @@ -8130,9 +10597,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.21.tgz", - "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", "cpu": [ "x64" ], @@ -8146,9 +10613,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.21.tgz", - "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", "cpu": [ "arm" ], @@ -8162,12 +10629,15 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.21.tgz", - "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8178,12 +10648,15 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.21.tgz", - "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8194,12 +10667,15 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.21.tgz", - "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8210,12 +10686,15 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.21.tgz", - "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8226,12 +10705,15 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.21.tgz", - "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8242,12 +10724,15 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.21.tgz", - "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8258,9 +10743,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.21.tgz", - "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", "cpu": [ "arm64" ], @@ -8274,9 +10759,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.21.tgz", - "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", "cpu": [ "ia32" ], @@ -8290,9 +10775,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.21.tgz", - "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", "cpu": [ "x64" ], @@ -8321,14 +10806,27 @@ } }, "node_modules/@swc/types": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", @@ -8956,6 +11454,33 @@ } } }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@toon-format/toon": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@toon-format/toon/-/toon-2.3.0.tgz", @@ -9056,6 +11581,19 @@ "bun-types": "1.3.14" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -9067,6 +11605,16 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -9366,6 +11914,13 @@ "@types/unist": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -9387,6 +11942,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -9449,6 +12014,14 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, + "node_modules/@types/pegjs": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", + "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", @@ -9469,6 +12042,34 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/readable-stream": { + "version": "4.0.24", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.24.tgz", + "integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/safe-regex": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@types/safe-regex/-/safe-regex-1.1.6.tgz", @@ -9490,6 +12091,21 @@ "license": "MIT", "optional": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -9509,6 +12125,25 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -9801,6 +12436,48 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.7.tgz", + "integrity": "sha512-JVUD8X2tfDMWjcjLs4yVxxVrS8yR5vnh386GAXT9Qj79nBxxXSaHFQZg5FweLmT8HlPQ3kii6noUB+Z9RN7DvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -10099,6 +12776,16 @@ "d3-transition": "^3.0.1" } }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", @@ -10252,6 +12939,16 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, "node_modules/@xyflow/react": { "version": "12.11.2", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.2.tgz", @@ -10359,6 +13056,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true, + "license": "MIT" + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -10369,6 +13073,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -10403,6 +13120,41 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", + "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/afinn-165": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/afinn-165/-/afinn-165-2.0.2.tgz", + "integrity": "sha512-mJ/RLUfpXfQA6bzugv+bBsc/QYkVrKaLYeS8fWBpKbTCsonv4iuV9ET0fgReEunm9vKLkaNgnekuSNlTC3WQ1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/afinn-165-financialmarketnews": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/afinn-165-financialmarketnews/-/afinn-165-financialmarketnews-3.0.0.tgz", + "integrity": "sha512-0g9A1S3ZomFIGDTzZ0t6xmv4AuokBvBmpes8htiyHpH7N4xDmvSQL6UxL/Zcs2ypRb3VwgCscaD8Q3zEawKYhw==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/agent-base": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", @@ -10412,6 +13164,25 @@ "node": ">= 20" } }, + "node_modules/ai": { + "version": "6.0.225", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.225.tgz", + "integrity": "sha512-TCkt/NxyZFyXyHeGO/4FMsdTIoCdwV/e78jG9iEHIjnwrAHYlqOwVj7WDFsNRqXUeoUefgbiOkNuE5k8jdpFRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.149", + "@ai-sdk/provider": "3.0.14", + "@ai-sdk/provider-utils": "4.0.38", + "@opentelemetry/api": "^1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -10587,6 +13358,33 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/apparatus": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/apparatus/-/apparatus-0.0.10.tgz", + "integrity": "sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sylvester": ">= 0.0.8" + }, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -10792,6 +13590,17 @@ "dev": true, "license": "MIT" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/asn1js": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", @@ -10823,6 +13632,19 @@ "node": ">=12" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -10839,6 +13661,13 @@ "astring": "bin/astring" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -10902,6 +13731,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, "node_modules/axe-core": { "version": "4.12.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", @@ -11015,6 +13851,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "devOptional": true, "funding": [ { "type": "github", @@ -11029,8 +13866,17 @@ "url": "https://feross.org/support" } ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": "^4.5.0 || >= 5.9" + } }, "node_modules/baseline-browser-mapping": { "version": "2.10.13", @@ -11044,6 +13890,16 @@ "node": ">=6.0.0" } }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -11078,6 +13934,17 @@ "require-from-string": "^2.0.2" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "license": "Unlicense", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -11088,6 +13955,16 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bin-links": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.2.tgz", @@ -11105,6 +13982,35 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/binary-extensions": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-3.1.0.tgz", + "integrity": "sha512-Jvvd9hy1w+xUad8+ckQsWA/V1AoyubOvqn0aygjMOVM4BfIaRav1NFS3LsTSDaV4n4FtcCtQXvzep1E6MboqwQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -11288,6 +14194,19 @@ "node": ">=8" } }, + "node_modules/broker-factory": { + "version": "3.1.15", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.15.tgz", + "integrity": "sha512-ko+aWvgNuP49meGrdjUu7rC+Y+Wai3cCPxP3xWwHsHfehFjOh5ZQM2yC4gEB2UddeZ/YXhm0K1eG/L6fxym2Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1", + "worker-factory": "^7.0.50" + } + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -11322,6 +14241,17 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bson": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.3.1.tgz", + "integrity": "sha512-h/C0qe6857pQhcSJHLfsR1uYGj98Ge3wKAD3Ed9KqH3wcVh+BM4Jq4xISD7vs9OPuT07n+q3QQVjslJ286j6ag==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -11356,6 +14286,21 @@ "node": ">=8.0.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -11529,6 +14474,56 @@ "node": "20 || >=22" } }, + "node_modules/cache-manager": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.9.tgz", + "integrity": "sha512-d4vceEyYe95gPxEyQchlEOH9vJlkNRW8G6gzFzzMTxJK9PahYMhC9chrEqgZN0HulROjgw3IzmWVNk7Q7ytiGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "keyv": "^5.6.0" + } + }, + "node_modules/cache-manager/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -11732,6 +14727,17 @@ "dev": true, "license": "MIT" }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -11922,6 +14928,64 @@ "node": ">=10" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-progress/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-progress/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-spinners": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", @@ -12023,6 +15087,155 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clipboardy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", + "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^8.0.1", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/clipboardy/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/clipboardy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -12101,6 +15314,29 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -12160,6 +15396,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -12178,6 +15428,52 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -12225,6 +15521,13 @@ "node": ">=22.12.0" } }, + "node_modules/commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", + "dev": true, + "license": "MIT" + }, "node_modules/common-ancestor-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", @@ -12235,6 +15538,79 @@ "node": ">= 18" } }, + "node_modules/complex.js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz", + "integrity": "sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -12248,6 +15624,22 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/concurrently": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", @@ -12559,6 +15951,67 @@ "node": ">=20" } }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cross-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/cross-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -12573,6 +16026,17 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -12613,12 +16077,147 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/csv-parse": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-7.0.1.tgz", + "integrity": "sha512-+2z7Ar0APQ7Uu6fX4cn+pitRmxjZ1WPBcGmZFKmA74FCyi7Et/XZx8cjNQ5CjbZ4HCOxXCOpRBYvYH08Qa003A==", + "dev": true, + "license": "MIT" + }, "node_modules/csv-stringify": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.8.1.tgz", "integrity": "sha512-tZ6X6TKQyQgCo5OptXcyAbfN1pwmoxEqELPQ7KFazNErx7kiVsDK8o+VYRXhfMl4N9vvOOLXuioquR2MeP847A==", "license": "MIT" }, + "node_modules/ctrf": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ctrf/-/ctrf-0.2.1.tgz", + "integrity": "sha512-iUo/eHcM5yG8aBS3Miqce9NNiZCtmVZxPpgmZEJIZ96bubwj7IpZx3IqsDqCH2FZjR71EH2NLtbBhtfzDjpaUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "glob": "13.0.6", + "yargs": "18.0.0" + }, + "bin": { + "ctrf": "dist/cli/cli.js" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/ctrf/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ctrf/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ctrf/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ctrf/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ctrf/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ctrf/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/ctrf/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/cytoscape": { "version": "3.33.3", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.3.tgz", @@ -13146,6 +16745,16 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-uri-to-buffer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz", + "integrity": "sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, "node_modules/data-urls": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", @@ -13229,6 +16838,26 @@ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", "license": "MIT" }, + "node_modules/dayjs-plugin-utc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz", + "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-3.0.0.tgz", + "integrity": "sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -13276,8 +16905,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -13288,6 +16917,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -13332,6 +16976,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -13380,6 +17034,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-7.0.1.tgz", + "integrity": "sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", @@ -13530,6 +17202,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dpdm": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dpdm/-/dpdm-4.2.0.tgz", @@ -13668,6 +17353,132 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, + "node_modules/drizzle-orm": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -13687,8 +17498,35 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } }, "node_modules/ee-first": { "version": "1.1.1", @@ -13725,6 +17563,13 @@ "node": ">= 4" } }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -13768,6 +17613,99 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { "version": "5.21.6", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", @@ -14031,6 +17969,17 @@ "license": "MIT", "optional": true }, + "node_modules/es6-promisify": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-7.0.0.tgz", + "integrity": "sha512-ginqzK3J90Rd4/Yz7qRrqUeIpe3TwSXTPPZtPne7tGBPeAaQiU8qt4fpKApnxHcq1AwtUdHVg5P77x/yrggG8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -14131,6 +18080,13 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -14143,6 +18099,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "9.39.4", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", @@ -14684,6 +18673,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -14842,12 +18845,32 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/events-to-array": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-2.0.3.tgz", @@ -14871,9 +18894,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -15014,6 +19037,13 @@ "express": ">= 4.11" } }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "dev": true, + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -15105,6 +19135,13 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "dev": true, + "license": "Unlicense" + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -15122,6 +19159,20 @@ "fast-string-truncated-width": "^3.0.2" } }, + "node_modules/fast-unique-numbers": { + "version": "9.0.27", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz", + "integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", @@ -15148,6 +19199,57 @@ "fast-string-width": "^3.0.2" } }, + "node_modules/fast-xml-builder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.0.tgz", + "integrity": "sha512-SLhnTEqE5QpJHq/6zl9bsmImEP2adv+y6Wy+cJa7nVTRzQh1OZfCe9k29M5xN74LWnu0xa1zrUrq3KnOKl92Fg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.2.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -15185,6 +19287,45 @@ } } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-retry": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz", + "integrity": "sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/fetch-socks": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fetch-socks/-/fetch-socks-1.3.3.tgz", @@ -15230,6 +19371,26 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "21.3.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.2.tgz", + "integrity": "sha512-DLkUvGwep3poOV2wpzbHCOnSKGk1LzyXTv+aHFgN2VFl96wnp8YA9YjO2qPzg5PuL8q/SW9Pdi6WTkYOIh995w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -15237,6 +19398,16 @@ "license": "MIT", "optional": true }, + "node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 10.4.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -15321,6 +19492,13 @@ "dev": true, "license": "ISC" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true, + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -15427,6 +19605,19 @@ "node": ">=18.3.0" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -15436,6 +19627,20 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/framer-motion": { "version": "12.42.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", @@ -15795,6 +20000,90 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.3.tgz", + "integrity": "sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -15929,6 +20218,21 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.1.tgz", + "integrity": "sha512-/5N/P4Lrh0p/mDwlDRi7Y1+P2o/OyzZI3l6Iz1Ov6XXwwm1y3RlZLuo3gVgML99djrEDtV980bBxSuOeHLk8ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.3.1", + "data-uri-to-buffer": "8.0.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -16097,6 +20401,257 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/globby/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-auth-library/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/google-auth-library/node_modules/gaxios": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", + "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-auth-library/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "dev": true, + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/googleapis-common": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2.tgz", + "integrity": "sha512-5MXeQzIZaqCH7B+HJWqhQm946VARpZep6acbWSr/fcgF2cQANq7allgX+i/G0EqF0WyUxB277gtWMzRYHMl9tg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "gaxios": "7.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", + "qs": "^6.7.0", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -16109,6 +20664,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/gpt-tokenizer": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/gpt-tokenizer/-/gpt-tokenizer-3.4.0.tgz", @@ -16121,6 +20702,43 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/grpc-js-reflection-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/grpc-js-reflection-client/-/grpc-js-reflection-client-1.4.0.tgz", + "integrity": "sha512-zkypuxNu0u53rhiS9PpP0ih8gZTmy/+yxHU6U+isWBX311YcE2nzn9xA4prkGsNYf5d8Q5MF2duVbvS/HMm9cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "google-protobuf": "^4.0.2", + "lodash": "^4.18.1", + "protobufjs": "^8.0.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.14.3" + } + }, + "node_modules/grpc-js-reflection-client/node_modules/google-protobuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.2.tgz", + "integrity": "sha512-yD2fqbNgvJPuQwdKJiPdbUcXveNRxgqy070gzsBsCyFJA8Qdj9oxa9xtkddb/JEhcDk0RD5SfGUWg+nhINfMxA==", + "dev": true, + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/guid-typescript": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", @@ -16212,6 +20830,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -16489,6 +21120,23 @@ "node": ">=16.9.0" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hookpoint": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hookpoint/-/hookpoint-4.1.0.tgz", + "integrity": "sha512-nKlElb27LmBWAVWirkcX8cQNGAPLsBe0pk4uTBt9faaOTFyUsMCWtKq6gAx8GlapPTGQ0rOnbIJQFSWqX1xIuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/hosted-git-info": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", @@ -16619,6 +21267,38 @@ "node": "^22.15.0 || ^24.0.0 || >=26.0.0" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/http-z": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/http-z/-/http-z-8.1.1.tgz", + "integrity": "sha512-4rEIu4SljSAs+lgCzzskyNdYllteGIHdnMBsu9MqafivyPAofSmCsrRjHQgxLs0BoPkUJBa7Ld6rXP32SPI8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", @@ -16639,6 +21319,187 @@ "integrity": "sha512-URfeibL0kTH6VuIxxaJDXWQWEk8fKr+9L8MGv6CuAiNy0fGnoVhWbXBvJR1mkdsvCDUxvhX9cW60k2AhtH5s6w==", "license": "MIT" }, + "node_modules/httpyac": { + "version": "6.16.7", + "resolved": "https://registry.npmjs.org/httpyac/-/httpyac-6.16.7.tgz", + "integrity": "sha512-aJooJeQioieWTB8LAqUNFIUo5WM8yJzLEoPtBZFhvvCZEXo5r5/sswMFfX1Et7P6IrISRQywU3a4s8unjfAqpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cloudamqp/amqp-client": "^2.1.1", + "@grpc/grpc-js": "^1.12.2", + "@grpc/proto-loader": "^0.7.13", + "@xmldom/xmldom": "^0.9.5", + "aws4": "^1.13.2", + "chalk": "^4.1.2", + "clipboardy": "^4.0.0", + "commander": "^12.1.0", + "dayjs": "^1.11.13", + "dayjs-plugin-utc": "^0.1.2", + "dotenv": "^16.4.5", + "encodeurl": "^2.0.0", + "eventsource": "^2.0.2", + "filesize": "^10.1.6", + "globby": "^14.0.2", + "google-protobuf": "^3.21.4", + "got": "^11.8.6", + "grpc-js-reflection-client": "^1.2.20", + "hookpoint": "4.1.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "inquirer": "^12.0.1", + "kafkajs": "^2.2.4", + "lodash": "^4.17.21", + "mqtt": "^5.10.1", + "open": "^8.4.2", + "socks-proxy-agent": "^8.0.4", + "tough-cookie": "^5.0.0", + "uuid": "^11.0.1", + "ws": "^8.18.0", + "xmldom-format": "^2.0.0", + "xpath": "^0.0.34" + }, + "bin": { + "httpyac": "bin/httpyac.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/httpyac/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/httpyac/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/httpyac/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/httpyac/node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/httpyac/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/httpyac/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/httpyac/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/httpyac/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/httpyac/node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/httpyac/node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/httpyac/node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -16665,6 +21526,216 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/ibm-cloud-sdk-core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.5.0.tgz", + "integrity": "sha512-ot6sGHAvSnd/ZSU4ZFn+m7i+xcFo3pmA3qm2JmEndDkMsuNR8HLdUeJ5iBbmkUfCGbf2ccTu/GvoJgFetyO6XA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@types/debug": "4.1.12", + "@types/node": "18.19.80", + "@types/tough-cookie": "4.0.0", + "axios": "1.18.0", + "camelcase": "6.3.0", + "debug": "4.3.4", + "dotenv": "16.4.5", + "extend": "3.0.2", + "file-type": "21.3.2", + "form-data": "4.0.6", + "isstream": "0.1.2", + "jsonwebtoken": "9.0.3", + "load-esm": "1.0.3", + "mime-types": "2.1.35", + "retry-axios": "2.6.0", + "tough-cookie": "4.1.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "18.19.80", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", + "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ibm-cloud-sdk-core/node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ibm-cloud-sdk-core/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -16700,6 +21771,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, "funding": [ { "type": "github", @@ -16714,8 +21786,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -17126,6 +22197,453 @@ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, + "node_modules/inquirer": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", + "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/prompts": "^7.10.1", + "@inquirer/type": "^3.0.10", + "mute-stream": "^2.0.0", + "run-async": "^4.0.6", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/inquirer/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -17307,6 +22825,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/is-bun-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", @@ -17418,6 +22944,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/is-expression": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", @@ -17828,6 +23362,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -17889,6 +23436,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -17902,6 +23465,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -17954,6 +23525,24 @@ "node": ">=8" } }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -17978,7 +23567,6 @@ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", - "optional": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -17989,6 +23577,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true, + "license": "MIT" + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -17999,6 +23594,19 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jks-js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/jks-js/-/jks-js-1.1.7.tgz", + "integrity": "sha512-BeiDRKsAi1NwEwgx2JB/9/0tar5BNGIv+foGm1G5GgiyR35s/iUnfd/BWqYd16mLDD8qTaAVBrIcOOuVqXJZNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-forge": "^1.4.0", + "node-int64": "^0.4.0", + "node-rsa": "^1.1.1" + } + }, "node_modules/joi": { "version": "18.2.1", "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz", @@ -18036,6 +23644,13 @@ "node": ">=10" } }, + "node_modules/js-base64": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.8.1.tgz", + "integrity": "sha512-5xVjhUZlHHeuO2W7w2rDFj/Kl1xLX+HjZxdOQwCsUOifl6UaoH1o1wsbsTMz+r0aeC7gCijvru02j6TfKZWzKg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", @@ -18043,6 +23658,27 @@ "dev": true, "license": "MIT" }, + "node_modules/js-rouge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/js-rouge/-/js-rouge-3.2.0.tgz", + "integrity": "sha512-2dvY28iFq5NcwxPNzc2zMgLVJED843m6CnKrCy0jYnOKd+QQhdkxI1wmdQspbcOAggo3K3gUZfhTSwmM+lWoBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -18194,6 +23830,16 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -18214,6 +23860,27 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -18292,6 +23959,44 @@ ], "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -18336,6 +24041,243 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/junit-to-ctrf": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/junit-to-ctrf/-/junit-to-ctrf-0.0.14.tgz", + "integrity": "sha512-gVrJaMKhE2tKuHh9Of/Le1Y6s6U8aZy9HUapW+IjbltyvomsGBoabv0Ul7xfTlZshYCYGbwx6OWFNm2Q66EaHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ctrf": "^0.0.17", + "fs-extra": "^11.3.0", + "glob": "^11.0.3", + "typescript": "^5.8.3", + "xml2js": "^0.6.2", + "yargs": "^18.0.0" + }, + "bin": { + "junit-to-ctrf": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/junit-to-ctrf/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/junit-to-ctrf/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/junit-to-ctrf/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/junit-to-ctrf/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/junit-to-ctrf/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/junit-to-ctrf/node_modules/ctrf": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/ctrf/-/ctrf-0.0.17.tgz", + "integrity": "sha512-PPk9b+AuA+UoBcbzSQWXMIuh5601zDHgXlmHIG8ESxTUnnb0eM2sz8H3jQLYQZTpyIaZVCLFZFslIDB1EMVZ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "glob": "^11.0.3", + "typescript": "^5.8.3", + "yargs": "^18.0.0" + }, + "bin": { + "ctrf": "dist/cli.js" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/junit-to-ctrf/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/junit-to-ctrf/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/junit-to-ctrf/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/junit-to-ctrf/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/junit-to-ctrf/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/junit-to-ctrf/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/junit-to-ctrf/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/junit-to-ctrf/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/just-diff": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", @@ -18350,6 +24292,52 @@ "dev": true, "license": "MIT" }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/kareem": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.3.0.tgz", + "integrity": "sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/katex": { "version": "0.16.45", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz", @@ -18397,6 +24385,24 @@ "json-buffer": "3.0.1" } }, + "node_modules/keyv-file": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.3.5.tgz", + "integrity": "sha512-0JFTTi55d1HdhIrSOnPngUw0fyHLn3BHqoLJ8TyGKM/fQfuZsz8HkcFxpl6YzU2mj1ZfRF/6BXlSqOpyfXYAmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1", + "tslib": "^1.14.1" + } + }, + "node_modules/keyv-file/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -18465,6 +24471,13 @@ "url": "https://liberapay.com/Koromix" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true, + "license": "MIT" + }, "node_modules/ky": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", @@ -18477,6 +24490,34 @@ "url": "https://github.com/sindresorhus/ky?sponsor=1" } }, + "node_modules/langfuse": { + "version": "3.38.20", + "resolved": "https://registry.npmjs.org/langfuse/-/langfuse-3.38.20.tgz", + "integrity": "sha512-MAmBAASSzJtmK1O9HQegA1mFsQhT8Yf+OJRGvE7FXkyv3g/eiBE0glLD0Ohg3pkxhoPdggM5SejK7ue9ctlaMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "langfuse-core": "^3.38.20" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/langfuse-core": { + "version": "3.38.20", + "resolved": "https://registry.npmjs.org/langfuse-core/-/langfuse-core-3.38.20.tgz", + "integrity": "sha512-zBKVmQN/1oT5VWZUBYlWzvokIlkC/6mnpgr/2atMyTeAm+jR3ia7w2iJMjlrF5/oG8ukO1s8+LDRCzJpF1QeEA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mustache": "^4.2.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -18532,6 +24573,49 @@ "node": ">= 0.8.0" } }, + "node_modules/libsql": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.29.tgz", + "integrity": "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==", + "cpu": [ + "x64", + "arm64", + "wasm32", + "arm" + ], + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.5.29", + "@libsql/darwin-x64": "0.5.29", + "@libsql/linux-arm-gnueabihf": "0.5.29", + "@libsql/linux-arm-musleabihf": "0.5.29", + "@libsql/linux-arm64-gnu": "0.5.29", + "@libsql/linux-arm64-musl": "0.5.29", + "@libsql/linux-x64-gnu": "0.5.29", + "@libsql/linux-x64-musl": "0.5.29", + "@libsql/win32-x64-msvc": "0.5.29" + } + }, + "node_modules/libsql/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/libxmljs2": { "version": "0.37.0", "resolved": "https://registry.npmjs.org/libxmljs2/-/libxmljs2-0.37.0.tgz", @@ -19273,6 +25357,27 @@ "node": ">=22.13.0" } }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=13.2.0" + } + }, "node_modules/loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", @@ -19432,6 +25537,13 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -19446,6 +25558,54 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -19453,6 +25613,14 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -19555,6 +25723,34 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -19600,6 +25796,16 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -19789,6 +25995,43 @@ "node": ">= 0.4" } }, + "node_modules/mathjs": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.2.0.tgz", + "integrity": "sha512-UAQzSVob9rNLdGpqcFMYmSu9dkuLYy7Lr2hBEQS5SHQdknA9VppJz3cy2KkpMzTODunad6V6cNv+5kOLsePLow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.26.10", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -20104,6 +26347,25 @@ "node": ">= 0.8" } }, + "node_modules/memjs": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/memjs/-/memjs-1.3.2.tgz", + "integrity": "sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -20966,8 +27228,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -21201,6 +27463,133 @@ "node": ">= 18" } }, + "node_modules/mongodb": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz", + "integrity": "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.2.0", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.7.4.tgz", + "integrity": "sha512-nuSYGUWWzNd4EAbGYxE469wPTL+kmxb5+91YvCvMkJ08rvNRht/usZUU3LuFuk7rDutF2QWBZHPHuzM8TxXApA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "kareem": "3.3.0", + "mongodb": "~7.2", + "mpath": "0.9.0", + "mquery": "6.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/moo": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", @@ -21250,12 +27639,194 @@ "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", "license": "MIT" }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mqtt": { + "version": "5.15.2", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.2.tgz", + "integrity": "sha512-VWZU2CSUY3U3oN0PSBRDE5SNsFi4zqqNeQ/uv3pZWqY3CrBXD/dhd0ZLjlsk5YnebGlrapi4lRVNJPUNQ5aZ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.21", + "@types/ws": "^8.18.1", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.4.1", + "help-me": "^5.0.0", + "lru-cache": "^10.4.3", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.2", + "number-allocator": "^1.0.14", + "readable-stream": "^4.7.0", + "rfdc": "^1.4.1", + "socks": "^2.8.6", + "split2": "^4.2.0", + "worker-timers": "^8.0.23", + "ws": "^8.18.3" + }, + "bin": { + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/mqtt-packet": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", + "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt-packet/node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/mqtt-packet/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/mqtt-packet/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/mqtt/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/mqtt/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/mquery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", + "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mutation-server-protocol": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz", @@ -21373,6 +27944,33 @@ "url": "https://opencollective.com/napi-postinstall" } }, + "node_modules/natural": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/natural/-/natural-8.1.1.tgz", + "integrity": "sha512-Ucb+lsUcGxUqu3rn8cwHjT6gJQosO63nIX/aBQXB3+IDkNbFV7PuviysO+Rzz3aKn7PZhPj3bNF4PS9gDVjYCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "afinn-165": "^2.0.2", + "afinn-165-financialmarketnews": "^3.0.0", + "apparatus": "^0.0.10", + "dotenv": "^17.3.1", + "memjs": "^1.3.2", + "mongoose": "^9.2.1", + "pg": "^8.18.0", + "redis": "^5.11.0", + "safe-stable-stringify": "^2.5.0", + "stopwords-iso": "^1.1.0", + "sylvester": "^0.0.21", + "underscore": "^1.13.0", + "uuid": "^13.0.0", + "wordnet-db": "^3.1.14" + }, + "engines": { + "node": ">=0.4.10" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -21380,6 +27978,20 @@ "dev": true, "license": "MIT" }, + "node_modules/natural/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -21421,6 +28033,16 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/next": { "version": "16.2.10", "resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz", @@ -21563,6 +28185,27 @@ "license": "MIT", "optional": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-emoji": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", @@ -21643,6 +28286,17 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "optional": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-gyp": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", @@ -21743,6 +28397,14 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/node-loader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-2.1.0.tgz", @@ -21779,6 +28441,17 @@ "node": ">=18" } }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "asn1": "^0.2.4" + } + }, "node_modules/node-sarif-builder": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-4.1.0.tgz", @@ -21793,6 +28466,21 @@ "node": ">=20" } }, + "node_modules/node-sql-parser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.4.0.tgz", + "integrity": "sha512-jVe6Z61gPcPjCElPZ6j8llB3wnqGcuQzefim1ERsqIakxnEy5JlzV7XKdO1KmacRG5TKwPc4vJTgSRQ0LfkbFw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@types/pegjs": "^0.10.0", + "big-integer": "^1.6.48" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", @@ -21847,6 +28535,19 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-bundled": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", @@ -22001,6 +28702,53 @@ "node": ">=8" } }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -22179,6 +28927,16 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -22188,6 +28946,16 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -22294,6 +29062,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "6.46.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.46.0.tgz", + "integrity": "sha512-DFg6jEPT2RO+oAyXtddeUJU8zkGy1OQ1AjGzNIJUMQG03TTqvCpy9tBpQ+2VVVnvrl3E56F8GEin2JYtWpITtA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openapi-fetch": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.8.2.tgz", + "integrity": "sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "openapi-typescript-helpers": "^0.0.5" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.5.tgz", + "integrity": "sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -22346,6 +29174,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -22443,6 +29282,27 @@ "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -22488,6 +29348,165 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue-compat": { + "version": "1.0.225", + "resolved": "https://registry.npmjs.org/p-queue-compat/-/p-queue-compat-1.0.225.tgz", + "integrity": "sha512-SdfGSQSJJpD7ZR+dJEjjn9GuuBizHPLW/yarJpXnmrHRruzrq7YM8OqsikSrKeoPv+Pi1YXw9IIBSIg5WveQHA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "eventemitter3": "5.x", + "p-timeout-compat": "^1.0.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/p-timeout-compat/-/p-timeout-compat-1.0.8.tgz", + "integrity": "sha512-+7LpKr1ilnWU0LbV2r+Wz4srwMcFTUysmgL824ZxJcZP3u4Hyi/D/39pbyEs4j0XXCHvbv069+LDPxlCijfVRQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/pac-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", + "integrity": "sha512-1aU+1mpj3DrQPfo3gh+3Gap3G5x+axnMx1P/y0ZF2ch7kb2meyOCAH8K2k9d27ROsTE7TnAerzxqF9aon2jqnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "get-uri": "8.0.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "pac-resolver": "9.0.1", + "quickjs-wasi": "^2.2.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-resolver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-9.0.1.tgz", + "integrity": "sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "7.0.1", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, "node_modules/package-json": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", @@ -22511,8 +29530,7 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "license": "BlueOak-1.0.0", - "optional": true + "license": "BlueOak-1.0.0" }, "node_modules/package-json/node_modules/semver": { "version": "7.8.0", @@ -22725,6 +29743,22 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -22792,6 +29826,163 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, + "node_modules/pem": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.8.tgz", + "integrity": "sha512-ZpbOf4dj9/fQg5tQzTqv4jSKJQsK7tPl0pm4/pvPcZVjZcJg7TMfr3PBk6gJH97lnpJDu4e4v8UUqEz5daipCg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es6-promisify": "^7.0.0", + "md5": "^2.3.0", + "os-tmpdir": "^1.0.2", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pg": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.15.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "dev": true, + "license": "MIT", + "optional": true, + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -22969,6 +30160,42 @@ "node": ">=18" } }, + "node_modules/playwright-ctrf-json-reporter": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/playwright-ctrf-json-reporter/-/playwright-ctrf-json-reporter-0.0.29.tgz", + "integrity": "sha512-zwJx7y/StmMtVq4DHbn85II/YbhnufyL08CeBH0sPzwq2nmHxsaRj+Te+ISimnWsHptrvcWRIfueh1LMjVwuIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ctrf": "^0.2.0" + } + }, + "node_modules/playwright-extra": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/playwright-extra/-/playwright-extra-4.3.6.tgz", + "integrity": "sha512-q2rVtcE8V8K3vPVF1zny4pvwZveHLH8KBuVU2MoE3Jw4OKVoBWsHI9CH9zPydovHHOCDxjGN2Vg+2m644q3ijA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "playwright": "*", + "playwright-core": "*" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + }, + "playwright-core": { + "optional": true + } + } + }, "node_modules/po-parser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", @@ -23055,6 +30282,66 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posthog-node": { + "version": "5.24.17", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.17.tgz", + "integrity": "sha512-mdb8TKt+YCRbGQdYar3AKNUPCyEiqcprScF4unYpGALF6HlBaEuO6wPuIqXXpCWkw4VclJYCKbb6lq6pH6bJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/core": "1.23.1" + }, + "engines": { + "node": "^20.20.0 || >=22.22.0" + } + }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", @@ -23147,6 +30434,23 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -23213,6 +30517,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "dev": true, + "license": "ISC" + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -23228,6 +30539,1091 @@ "node": ">=10" } }, + "node_modules/promptfoo": { + "version": "0.121.18", + "resolved": "https://registry.npmjs.org/promptfoo/-/promptfoo-0.121.18.tgz", + "integrity": "sha512-avytaJ3Vi043Cp/LHRNstKK7PzaDso5QvPa1llMAsISfG8uC7w3mKATGlLcO8Qo6SIhmibfz2JUQvG0EuFuJ0g==", + "dev": true, + "license": "MIT", + "workspaces": [ + "src/app", + "site" + ], + "dependencies": { + "@anthropic-ai/sdk": "0.106.0", + "@apidevtools/json-schema-ref-parser": "^15.3.1", + "@inquirer/checkbox": "^5.1.0", + "@inquirer/confirm": "^6.0.8", + "@inquirer/core": "^11.1.5", + "@inquirer/editor": "^5.0.8", + "@inquirer/input": "^5.0.8", + "@inquirer/search": "^4.1.8", + "@inquirer/select": "^5.1.0", + "@libsql/client": "^0.17.3", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.219.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-trace-base": "^2.6.0", + "@opentelemetry/sdk-trace-node": "^2.6.0", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@types/ws": "^8.18.1", + "ai": "^6.0.190", + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "async": "^3.2.6", + "binary-extensions": "^3.1.0", + "cache-manager": "^7.2.8", + "chalk": "^5.6.2", + "chokidar": "5.0.0", + "cli-progress": "^3.12.0", + "cli-table3": "^0.6.5", + "commander": "^14.0.3", + "compression": "^1.8.1", + "cors": "^2.8.6", + "csv-parse": "^7.0.0", + "csv-stringify": "^6.7.0", + "debounce": "^3.0.0", + "dedent": "^1.7.2", + "dotenv": "^17.3.1", + "drizzle-orm": "^0.45.1", + "execa": "^9.6.1", + "express": "^5.2.1", + "exsolve": "^1.0.8", + "fast-deep-equal": "^3.1.3", + "fast-safe-stringify": "^2.1.1", + "fast-xml-parser": "^5.7.1", + "fastest-levenshtein": "^1.0.16", + "gcp-metadata": "^8.1.2", + "glob": "^13.0.6", + "http-z": "^8.1.1", + "istextorbinary": "^9.5.0", + "js-rouge": "^3.2.0", + "js-yaml": "5.2.0", + "json5": "^2.2.3", + "keyv": "^5.6.0", + "keyv-file": "^5.3.3", + "lru-cache": "^11.3.0", + "mathjs": "^15.1.1", + "minimatch": "^10.2.4", + "nunjucks": "^3.2.4", + "openai": "^6.37.0", + "opener": "^1.5.2", + "ora": "^9.3.0", + "parse5": "^8.0.0", + "posthog-node": "~5.24.10", + "protobufjs": "^8.0.0", + "proxy-agent": "^8.0.0", + "proxy-from-env": "^2.1.0", + "python-shell": "^5.0.0", + "rfdc": "^1.4.1", + "rxjs": "^7.8.2", + "saxes": "^6.0.0", + "semver": "^7.7.4", + "simple-git": "^3.33.0", + "socket.io": "^4.8.3", + "socket.io-client": "^4.8.3", + "text-extensions": "^3.1.0", + "tsx": "^4.21.0", + "undici": ">=7.28.0 <8", + "winston": "^3.19.0", + "ws": "^8.19.0", + "zod": "^4.3.6" + }, + "bin": { + "pf": "dist/src/entrypoint.js", + "promptfoo": "dist/src/entrypoint.js" + }, + "engines": { + "node": "^20.20.0 || >=22.22.0" + }, + "optionalDependencies": { + "@anthropic-ai/claude-agent-sdk": "0.3.195", + "@aws-sdk/client-bedrock-agent-runtime": "^3.1045.0", + "@aws-sdk/client-bedrock-runtime": "^3.1045.0", + "@aws-sdk/client-s3": "^3.1003.0", + "@aws-sdk/client-sagemaker-runtime": "^3.1045.0", + "@aws-sdk/credential-provider-sso": "^3.972.16", + "@azure/ai-projects": "^2.1.1", + "@azure/identity": "^4.13.0", + "@azure/msal-node": "^5.2.0", + "@azure/openai-assistants": "^1.0.0-beta.6", + "@azure/storage-blob": "^12.31.0", + "@fal-ai/client": "~1.10.1", + "@googleapis/sheets": "^13.0.1", + "@huggingface/transformers": "^4.0.0", + "@ibm-cloud/watsonx-ai": "^1.7.14", + "@ibm-generative-ai/node-sdk": "^3.2.4", + "@modelcontextprotocol/sdk": "^1.29.0", + "@openai/agents": "^0.11.3", + "@openai/codex-sdk": "^0.142.3", + "@opencode-ai/sdk": "^1.14.33", + "@playwright/browser-chromium": "^1.60.0", + "@rollup/rollup-linux-x64-gnu": "^4.62.0", + "@slack/web-api": "^7.15.2", + "@smithy/node-http-handler": "^4.4.14", + "@swc/core": "^1.15.41", + "@swc/core-darwin-arm64": "^1.15.41", + "@swc/core-darwin-x64": "^1.15.41", + "@swc/core-linux-x64-gnu": "^1.15.41", + "@swc/core-linux-x64-musl": "^1.15.41", + "@swc/core-win32-x64-msvc": "^1.15.41", + "google-auth-library": "^10.9.0", + "hono": "^4.12.25", + "ibm-cloud-sdk-core": "^5.4.22", + "jks-js": "^1.1.5", + "langfuse": "^3.38.20", + "natural": "^8.1.1", + "node-sql-parser": "^5.4.0", + "pdf-parse": "^2.4.5", + "pem": "~1.14.8", + "playwright": "^1.60.0", + "playwright-extra": "^4.3.6", + "read-excel-file": "^9.0.0", + "sharp": "^0.35.1" + } + }, + "node_modules/promptfoo/node_modules/@huggingface/jinja": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz", + "integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/promptfoo/node_modules/@huggingface/transformers": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz", + "integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@huggingface/jinja": "^0.5.6", + "@huggingface/tokenizers": "^0.1.3", + "onnxruntime-node": "1.24.3", + "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", + "sharp": "^0.34.5" + } + }, + "node_modules/promptfoo/node_modules/@huggingface/transformers/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/promptfoo/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/promptfoo/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/promptfoo/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/promptfoo/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/promptfoo/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/promptfoo/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/promptfoo/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promptfoo/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/promptfoo/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promptfoo/node_modules/js-yaml": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.0.tgz", + "integrity": "sha512-YeLUMlvR4Ou1B119LIaM0r65JvbOBooJDc9yEu0dClb/uSC5P4FrLU8OCCz/HXWvtPoIrR0dRzABTjo1sTN9Bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/promptfoo/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/promptfoo/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/promptfoo/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/promptfoo/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promptfoo/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promptfoo/node_modules/onnxruntime-common": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz", + "integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/promptfoo/node_modules/onnxruntime-node": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz", + "integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "adm-zip": "^0.5.16", + "global-agent": "^3.0.0", + "onnxruntime-common": "1.24.3" + } + }, + "node_modules/promptfoo/node_modules/onnxruntime-web": { + "version": "1.26.0-dev.20260416-b7804b056c", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz", + "integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.24.0-dev.20251116-b39e144322", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/promptfoo/node_modules/onnxruntime-web/node_modules/onnxruntime-common": { + "version": "1.24.0-dev.20251116-b39e144322", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz", + "integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/promptfoo/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promptfoo/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promptfoo/node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/sharp/node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/promptfoo/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promptfoo/node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -23267,9 +31663,9 @@ "version": "7.6.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "devOptional": true, "hasInstallScript": true, "license": "BSD-3-Clause", - "optional": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -23291,8 +31687,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0", - "optional": true + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -23307,6 +31703,26 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.2.tgz", + "integrity": "sha512-idLLRewuemWd7GH/BDJzGiB0dWGfT2SQs3jy6NtZtGWU9uPTTSdeC1/cdbqLwgzhfv027daGFuXX426e2Eg20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "9.1.0", + "proxy-from-env": "^2.0.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/proxy-agent-negotiate": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", @@ -23324,6 +31740,46 @@ } } }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -23333,6 +31789,20 @@ "node": ">=10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/pug": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", @@ -23539,6 +32009,16 @@ "node": ">=16.0.0" } }, + "node_modules/python-shell": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-5.0.0.tgz", + "integrity": "sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -23554,6 +32034,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -23581,6 +32069,26 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quickjs-wasi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz", + "integrity": "sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==", + "dev": true, + "license": "MIT" + }, "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", @@ -23823,12 +32331,28 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/read-excel-file": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/read-excel-file/-/read-excel-file-9.3.1.tgz", + "integrity": "sha512-yzC1vJ/yl3PGJfCDrOI6/rBagF0bRm/CK1NTNXYdomB+13mDB9SFyoRibsDXxDAFrwCANfuRqzuUWDljkkSEuQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fflate": "^0.8.3", + "saxen": "^11.0.2", + "unzipper-esm": "^0.13.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -23971,6 +32495,24 @@ "node": ">=8" } }, + "node_modules/redis": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.12.1.tgz", + "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@redis/bloom": "5.12.1", + "@redis/client": "5.12.1", + "@redis/json": "5.12.1", + "@redis/search": "5.12.1", + "@redis/time-series": "5.12.1" + }, + "engines": { + "node": ">= 18.19.0" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -24305,6 +32847,14 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/reselect": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", @@ -24331,6 +32881,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -24350,6 +32907,19 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -24388,6 +32958,20 @@ "node": ">= 4" } }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -24406,6 +32990,94 @@ "dev": true, "license": "MIT" }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -24431,6 +33103,14 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/robot3": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/robot3/-/robot3-0.4.1.tgz", + "integrity": "sha512-hzjy826lrxzx8eRgv80idkf8ua1JAepRc9Efdtj03N3KNJuznQCPlyCJ7gnUmDFwZCLQjxy567mQVKmdv2BsXQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/robust-predicates": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", @@ -24511,6 +33191,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/run-async": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -24575,6 +33265,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -24589,8 +33280,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/safe-push-apply": { "version": "1.0.0", @@ -24651,6 +33341,27 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxen": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-11.0.2.tgz", + "integrity": "sha512-WDb4gqac8uiJzOdOdVpr9NWh9NrJMm7Brn5GX2Poj+mjE/QTXqYQENr8T/mom54dDDgbd3QjwTg23TRHYiWXRA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 20.12" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -24725,8 +33436,8 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/selfsigned": { "version": "5.5.0", @@ -25070,6 +33781,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -25154,6 +33873,24 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/size-limit": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-12.1.0.tgz", @@ -25194,6 +33931,19 @@ "node": ">=8" } }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/slice-ansi": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", @@ -25261,6 +34011,113 @@ "node": ">=0.10" } }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/socks": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", @@ -25344,6 +34201,17 @@ "dev": true, "license": "(WTFPL OR MIT)" }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spdx-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", @@ -25564,6 +34432,16 @@ "dev": true, "license": "MIT" }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -25598,6 +34476,17 @@ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/state-local": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", @@ -25658,12 +34547,23 @@ "node": ">= 0.4" } }, + "node_modules/stopwords-iso": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stopwords-iso/-/stopwords-iso-1.1.0.tgz", + "integrity": "sha512-I6GPS/E0zyieHehMRPQcqkiBMJKGgLta+1hREixhoLPqEA0AlVFiC43dl8uPpmkkeRdDMzYRWFWk5/l9x7nmNg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -25701,7 +34601,6 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -25716,8 +34615,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", @@ -25725,7 +34623,6 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -25736,7 +34633,6 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -25893,7 +34789,6 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -25959,6 +34854,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/stubborn-fs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", @@ -26074,6 +35003,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sylvester": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.21.tgz", + "integrity": "sha512-yUT0ukFkFEt4nb+NY+n2ag51aS/u9UHXoZw+A4jgD77/jzZsBoSDHuqysrVCBC4CYR4TYvUJq54ONpXgDBH8tA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -26081,6 +35020,19 @@ "dev": true, "license": "MIT" }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tagged-tag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", @@ -26288,6 +35240,42 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-extensions": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-3.1.0.tgz", + "integrity": "sha512-anOjtXr8OT5w4vc/2mP4AYTCE0GWc/21icGmaHtBHnI7pN7o01a/oqG9m06/rGzoAsDm/WNzggBpqptuCmRlZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -26321,6 +35309,13 @@ "node": ">=20" } }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -26436,6 +35431,26 @@ "dev": true, "license": "MIT" }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -26502,6 +35517,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -26512,6 +35537,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -26865,6 +35897,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-function": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz", + "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/typed-inject": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz", @@ -26892,6 +35934,13 @@ "node": ">= 16.0.0" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -26936,6 +35985,20 @@ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbash": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.1.tgz", @@ -27206,6 +36269,21 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, + "node_modules/unzipper-esm": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/unzipper-esm/-/unzipper-esm-0.13.2.tgz", + "integrity": "sha512-lt8GtgDYV8YcAFZNQuLyR2QvHI8C/TstpgsdjUn9ZxiWLJgn+e5uW6DsO3e/HUJVuWD57ZLLFMZ9xk26tePuHQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -27295,6 +36373,26 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "dev": true, + "license": "BSD", + "optional": true + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -27460,6 +36558,19 @@ "node": ">= 0.8" } }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -27777,6 +36888,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -27971,6 +37092,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -27997,6 +37166,68 @@ "node": ">=0.10.0" } }, + "node_modules/wordnet-db": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/wordnet-db/-/wordnet-db-3.1.14.tgz", + "integrity": "sha512-zVyFsvE+mq9MCmwXUWHIcpfbrHHClZWZiVOzKSxNJruIcFn2RbY55zkhiAMMxM8zCVSmtNiViq8FsAZSFpMYag==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/worker-factory": { + "version": "7.0.50", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.50.tgz", + "integrity": "sha512-hhwc0G+sFwM4qBuhJIUBn2p1Jf8v/FwmLUANBf/Q+Lt2uI8mfIZQhXaZQACodQD4R7Zp6cn/6702bIvNn2puJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1" + } + }, + "node_modules/worker-timers": { + "version": "8.0.33", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.33.tgz", + "integrity": "sha512-RQVlKkek80v8M6SHvdMKiywaXFmX3XTpYTXTw0r62PdXB06t74XNxcTuG8wsQwnjorAQymNQyyQ0rzv7PykEMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "tslib": "^2.8.1", + "worker-timers-broker": "^8.0.18", + "worker-timers-worker": "^9.0.15" + } + }, + "node_modules/worker-timers-broker": { + "version": "8.0.18", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.18.tgz", + "integrity": "sha512-FrjzDVX1wKfZN0gRbCFqv8VHuTncG4sbI/WGEg4tSSQeIsnwqg4YBYWMAHYLJtUDEYYmiK65UKFrVMVk2irDSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "broker-factory": "^3.1.15", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1", + "worker-timers-worker": "^9.0.15" + } + }, + "node_modules/worker-timers-worker": { + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.15.tgz", + "integrity": "sha512-KKUe7lZ/Aignr51H6hOUik8LwTnIgojH/1lwhli8A8qIEIyewogZTpNpMW5B6BF7nmwBOkUoTYgf1H3QShcjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "tslib": "^2.8.1", + "worker-factory": "^7.0.50" + } + }, "node_modules/wrap-ansi": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", @@ -28021,7 +37252,6 @@ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -28039,8 +37269,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", @@ -28048,7 +37277,6 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -28059,7 +37287,6 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28075,7 +37302,6 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -28202,6 +37428,46 @@ "node": ">=18" } }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlbuilder2": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", @@ -28248,6 +37514,46 @@ "dev": true, "license": "MIT" }, + "node_modules/xmldom-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmldom-format/-/xmldom-format-2.0.0.tgz", + "integrity": "sha512-1zDf0QyGmROs0c/X4ttFMkPeBV+SUYTcLgUj2kpDNKw2g0Ta3dZtFWWYuJR/hQNeUJH6/M/Q9VxOWJRrT0qRug==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@xmldom/xmldom": "^0.9.5" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xpath": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", @@ -28408,6 +37714,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", diff --git a/package.json b/package.json index b2f2af7389..937d70363d 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "build:release": "rm -rf .build dist && OMNIROUTE_BUILD_SHA=$(git rev-parse --short HEAD) npm run build && npm run build:cli && node scripts/build/write-build-sha.mjs", "build:native:tproxy": "cd src/mitm/tproxy/native && npx --yes node-gyp rebuild", "start": "node scripts/dev/run-next.mjs start", + "homolog": "node scripts/homolog/run.mjs", "lint": "eslint . --cache --cache-location .eslintcache --suppressions-location config/quality/eslint-suppressions.json", "lint:json": "node scripts/quality/run-eslint-json.mjs", "lint:md": "npx --yes markdownlint-cli2 \"docs/**/*.md\" \"*.md\" \"!docs/i18n\" \"!docs/research\"", @@ -129,6 +130,7 @@ "i18n:check-ui-coverage": "node scripts/i18n/check-ui-keys-coverage.mjs", "check:node-runtime": "node --import tsx scripts/check/check-supported-node-runtime.ts", "check:pack-artifact": "node --import tsx scripts/build/validate-pack-artifact.ts", + "check:pack-boot": "node scripts/check/check-pack-boot.mjs", "check:pack-policy": "node --import tsx scripts/build/validate-pack-artifact.ts --policy-only", "check:cli-i18n": "node scripts/check/check-cli-i18n.mjs", "check:openapi-coverage": "node scripts/check/check-openapi-coverage.mjs", @@ -183,6 +185,7 @@ "audit:electron": "npm --prefix electron audit --audit-level=critical && (npm --prefix electron audit --audit-level=high || echo '::warning::electron high-severity advisories present (non-blocking)')", "typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json", "typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json", + "check:dashboard-typecheck": "node scripts/check/check-dashboard-typecheck.mjs", "backfill-aggregation": "node --import tsx src/scripts/backfillAggregation.ts", "env:sync": "node scripts/dev/sync-env.mjs", "test:integration": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 tests/integration/*.test.ts \"tests/integration/combo-matrix/*.test.ts\"", @@ -328,21 +331,26 @@ "c8": "^11.0.0", "concurrently": "^10.0.3", "cross-env": "^10.1.0", + "ctrf": "^0.2.1", "dpdm": "^4.2.0", "eslint": "^9.39.4", "eslint-config-next": "16.2.10", "eslint-plugin-sonarjs": "^4.1.0", "fast-check": "^4.8.0", "glob": "^13.0.6", + "httpyac": "^6.16.7", "husky": "^9.1.7", "jscpd": "^4.2.5", "jsdom": "^29.1.1", + "junit-to-ctrf": "^0.0.14", "knip": "^6.18.0", "license-checker-rseidelsohn": "^5.0.1", "lint-staged": "^17.0.8", "lockfile-lint": "^5.0.0", "node-loader": "^2.1.0", + "playwright-ctrf-json-reporter": "^0.0.29", "prettier": "^3.8.3", + "promptfoo": "^0.121.18", "size-limit": "^12.1.0", "tailwindcss": "^4.3.0", "type-coverage": "^2.29.7", diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index b0d8892b27..7afe168b29 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -135,6 +135,11 @@ const EXTRA_MODULE_ENTRIES = [ src: ["scripts", "dev", "peer-stamp.mjs"], dest: ["peer-stamp.mjs"], }, + { + label: "main-server timeouts (server-ws.mjs dependency, #7003/#7065-class)", + src: ["scripts", "dev", "main-server-timeouts.mjs"], + dest: ["main-server-timeouts.mjs"], + }, { label: "HTTP method guard (server-ws.mjs dependency)", src: ["scripts", "dev", "http-method-guard.cjs"], diff --git a/scripts/build/pack-artifact-policy.ts b/scripts/build/pack-artifact-policy.ts index 88329052a1..cc60babe71 100644 --- a/scripts/build/pack-artifact-policy.ts +++ b/scripts/build/pack-artifact-policy.ts @@ -46,6 +46,7 @@ export const APP_STAGING_ALLOWED_EXACT_PATHS: string[] = [ "open-sse/services/compression/engines/llmlingua/onnxWorker.js", "package.json", "peer-stamp.mjs", + "main-server-timeouts.mjs", "responses-ws-proxy.mjs", "scripts/dev/sync-env.mjs", "scripts/dev/tls-options.mjs", @@ -152,6 +153,7 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [ "dist/server-ws.mjs", "dist/responses-ws-proxy.mjs", "dist/peer-stamp.mjs", + "dist/main-server-timeouts.mjs", "dist/http-method-guard.cjs", // #5452: regression guard — make check:pack-artifact fail loudly if the TLS // opt-in sidecar (imported by dist/server-ws.mjs) ever vanishes from the tarball. @@ -160,6 +162,12 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [ "dist/head-response-guard.cjs", "dist/webdav-handler.mjs", "bin/cli/program.mjs", + // Direct imports of bin/omniroute.mjs — bin/cli/ is only an allowlist PREFIX, so a + // file vanishing from the tarball never fails the unexpected-paths check; only these + // required entries make its absence loud (#7065 class; derived + enforced by + // tests/unit/pack-artifact-entrypoint-closures.test.ts). + "bin/cli/data-dir.mjs", + "bin/cli/utils/storageKeyProvision.mjs", "bin/mcp-server.mjs", "bin/nodeRuntimeSupport.mjs", "bin/omniroute.mjs", diff --git a/scripts/check/check-dashboard-typecheck.mjs b/scripts/check/check-dashboard-typecheck.mjs new file mode 100644 index 0000000000..454bb308e9 --- /dev/null +++ b/scripts/check/check-dashboard-typecheck.mjs @@ -0,0 +1,177 @@ +#!/usr/bin/env node +// scripts/check/check-dashboard-typecheck.mjs +// Dashboard-scoped typecheck gate (#7033). +// +// `typecheck:core` (the only blocking CI typecheck gate) runs against a curated +// 27-file `"files"` allowlist in tsconfig.typecheck-core.json — none of it lives +// under `src/app/(dashboard)`, and `next.config.mjs` sets +// `typescript.ignoreBuildErrors: true`, so `next build` never type-checks either. +// Net effect: orphaned-identifier regressions in dashboard TSX (deleted `useState` +// decls with live usages left behind) are invisible to both CI type-check paths +// and only surface at runtime — exactly what happened in #6625/#6909. +// +// This gate runs `tsc` scoped to `src/app/(dashboard)/**/*.{ts,tsx}` via +// tsconfig.typecheck-dashboard.json and diffs the result against a frozen +// per-file/per-TS-code count baseline (config/quality/dashboard-typecheck-baseline.json), +// following this repo's stale-enforcement allowlist convention (see +// scripts/check/check-known-symbols.ts). A live count that EXCEEDS the baselined +// count for a given (file, TS code) pair is a regression and fails the gate; a +// live count that is lower is an improvement and does not fail (use --update to +// ratchet the baseline down). +// +// Run: +// node scripts/check/check-dashboard-typecheck.mjs +// node scripts/check/check-dashboard-typecheck.mjs --update # re-freeze baseline + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +const ROOT = process.cwd(); +const TSCONFIG = path.join(ROOT, "tsconfig.typecheck-dashboard.json"); +const BASELINE_PATH = path.join(ROOT, "config/quality/dashboard-typecheck-baseline.json"); +const UPDATE = process.argv.includes("--update"); + +// Matches tsc --pretty false output lines, e.g.: +// src/app/(dashboard)/dashboard/foo.tsx(12,7): error TS2304: Cannot find name 'bar'. +const TSC_ERROR_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+):/; + +/** + * Parses raw `tsc --pretty false` stdout into a nested count map: + * { "": { "": } } + * + * Pure/exported for unit testing against synthetic tsc output — no child + * process involved here. + */ +export function parseTscOutput(raw) { + const counts = {}; + const lines = String(raw).split("\n"); + for (const line of lines) { + const match = TSC_ERROR_LINE.exec(line); + if (!match) continue; + const [, file, , , code] = match; + if (!counts[file]) counts[file] = {}; + counts[file][code] = (counts[file][code] || 0) + 1; + } + return counts; +} + +/** + * Compares live (file, TS code) error counts against a frozen baseline. + * Returns `{ regressions, improvements }`: + * - regressions: entries where live count > baselined count (or the pair is + * entirely new/unbaselined) — these fail the gate. + * - improvements: entries where live count < baselined count — informational, + * do not fail (use --update to ratchet the baseline down). + * + * Exported for unit testing. + */ +export function diffAgainstBaseline(live, baseline) { + const regressions = []; + const improvements = []; + + for (const [file, codes] of Object.entries(live)) { + for (const [code, liveCount] of Object.entries(codes)) { + const baselineCount = (baseline[file] && baseline[file][code]) || 0; + if (liveCount > baselineCount) { + regressions.push({ file, code, liveCount, baselineCount }); + } else if (liveCount < baselineCount) { + improvements.push({ file, code, liveCount, baselineCount }); + } + } + } + + for (const [file, codes] of Object.entries(baseline)) { + for (const [code, baselineCount] of Object.entries(codes)) { + const liveCount = (live[file] && live[file][code]) || 0; + if (liveCount === 0 && baselineCount > 0) { + improvements.push({ file, code, liveCount: 0, baselineCount }); + } + } + } + + return { regressions, improvements }; +} + +function runTsc() { + try { + const stdout = execFileSync( + process.platform === "win32" ? "npx.cmd" : "npx", + ["tsc", "--pretty", "false", "--noEmit", "-p", TSCONFIG], + { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, cwd: ROOT } + ); + return stdout; + } catch (err) { + // tsc exits non-zero when there are type errors — stdout still has the report. + if (err.stdout) return String(err.stdout); + throw err; + } +} + +function loadBaseline() { + if (!fs.existsSync(BASELINE_PATH)) return {}; + return JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8")); +} + +function writeBaseline(counts) { + fs.writeFileSync(BASELINE_PATH, JSON.stringify(counts, null, 2) + "\n"); +} + +function main() { + if (!fs.existsSync(TSCONFIG)) { + process.stderr.write(`[dashboard-typecheck] FAIL — tsconfig not found at ${TSCONFIG}\n`); + process.exit(2); + } + + console.log("[dashboard-typecheck] Running tsc scoped to src/app/(dashboard)/**…"); + const stdout = runTsc(); + const live = parseTscOutput(stdout); + const baseline = loadBaseline(); + const { regressions, improvements } = diffAgainstBaseline(live, baseline); + + const liveErrorCount = Object.values(live).reduce( + (sum, codes) => sum + Object.values(codes).reduce((s, c) => s + c, 0), + 0 + ); + console.log(`dashboardTypecheckErrors=${liveErrorCount}`); + + if (UPDATE) { + writeBaseline(live); + console.log(`[dashboard-typecheck] baseline rewritten (${liveErrorCount} errors frozen).`); + process.exit(0); + } + + if (improvements.length > 0) { + console.log( + `[dashboard-typecheck] ${improvements.length} baselined error(s) no longer present ` + + `— run 'node scripts/check/check-dashboard-typecheck.mjs --update' to ratchet the baseline down:\n` + + improvements + .map((i) => ` - ${i.file} ${i.code} (baseline ${i.baselineCount} -> live ${i.liveCount})`) + .join("\n") + ); + } + + if (regressions.length > 0) { + process.stderr.write( + `[dashboard-typecheck] FAIL — ${regressions.length} new/regressed TypeScript error(s) ` + + `under src/app/(dashboard)/ not covered by the frozen baseline:\n` + + regressions + .map((r) => ` ✗ ${r.file} ${r.code} (baseline ${r.baselineCount}, live ${r.liveCount})`) + .join("\n") + + `\n\nIf this is a genuine new dashboard TSX bug (e.g. an orphaned identifier), fix it.\n` + + `If it's pre-existing type looseness you're intentionally not fixing in this PR,\n` + + `do NOT widen the baseline for new regressions — that defeats the gate.\n` + ); + process.exit(1); + } + + console.log( + `[dashboard-typecheck] OK — ${liveErrorCount} pre-existing error(s), all within frozen baseline.` + ); + process.exit(0); +} + +if (import.meta.url === pathToFileURL(process.argv[1] || "").href) { + main(); +} diff --git a/scripts/check/check-env-doc-sync.mjs b/scripts/check/check-env-doc-sync.mjs index 197d8ddeb9..408c53d5ca 100644 --- a/scripts/check/check-env-doc-sync.mjs +++ b/scripts/check/check-env-doc-sync.mjs @@ -101,6 +101,15 @@ const IGNORE_FROM_CODE = new Set([ // ("http://192.168.0.15:20128" / null), never OmniRoute runtime config (#5151). "COMBO_LIVE_BASE_URL", "COMBO_LIVE_API_KEY", + // Homologation E2E suite (npm run homolog) vars — configured via the dedicated + // .env.homolog file (template: .env.homolog.example), never in the runtime .env. + // Test/ops-only signals against the homologation VPS, same class as COMBO_LIVE_*. + // See docs/ops/HOMOLOGATION.md. + "HOMOLOG_BASE_URL", + "HOMOLOG_ADMIN_PASSWORD", + "HOMOLOG_API_KEY", + "HOMOLOG_CRITICAL_PROVIDERS", + "HOMOLOG_EXPECT_VERSION", // update-notifier opt-out for the CLI binary. "OMNIROUTE_NO_UPDATE_NOTIFIER", // Headless CLI execution flag for Electron. @@ -132,6 +141,12 @@ const IGNORE_FROM_CODE = new Set([ "QA_LOCALES", "QA_REPORT_SUFFIX", "QA_ROUTES", + // Post-publish verifier (scripts/release/verify-published.mjs): env passed INTO the + // clean Docker container script (Hard Rule #13 env-option pattern) — release tooling + // internals, never OmniRoute runtime config. + "VERIFY_DEADLINE_S", + "VERIFY_PORT", + "VERIFY_VERSION", // Doctor diagnostic flags (no runtime behavior yet — placeholders). "OMNIROUTE_DOCTOR_HOST", "OMNIROUTE_DOCTOR_LIVENESS_URL", diff --git a/scripts/check/check-pack-boot.mjs b/scripts/check/check-pack-boot.mjs new file mode 100644 index 0000000000..62a4b78ab5 --- /dev/null +++ b/scripts/check/check-pack-boot.mjs @@ -0,0 +1,166 @@ +#!/usr/bin/env node +/** + * check:pack-boot — boot-smoke of the REAL npm tarball (#7065 class killer, WS1.2/T1). + * + * Three releases shipped a tarball that crashed on every boot (tls-options/3.8.41, + * head-response-guard VPS #7040 + npm #7065) because no gate ever EXECUTED the + * artifact: structure checks (check:pack-artifact) validate lists, not runtime. + * This gate packs the tree, installs the tarball into a clean prefix, boots the + * installed CLI and polls /api/monitoring/health until it proves the artifact + * starts — regardless of WHICH packaging list drifted. + * + * Requires a built dist/ (run after `npm run build:cli`, e.g. in the CI + * package-artifact job or `check:release-green --with-build`). Exit codes: + * 0 = boots and reports the right version · 1 = boot failed · 2 = missing build. + */ +import { execFileSync, spawn } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const POLL_INTERVAL_MS = 2_000; +const BOOT_DEADLINE_MS = 240_000; + +/** Parse `npm pack --json` output into the generated tarball filename. */ +export function pickTarball(packJsonOutput) { + const parsed = JSON.parse(packJsonOutput); + const filename = Array.isArray(parsed) ? parsed[0]?.filename : undefined; + if (!filename) throw new Error("npm pack --json returned no filename"); + // npm >=9 may emit scoped names with "/" — normalize to the on-disk file name. + return filename.replace(/\//g, "-"); +} + +/** + * Boot verdict: HTTP 200 + a JSON body reporting the version we just packed. + * `status` is logged but NOT asserted — a clean install with zero providers may + * legitimately report degraded states; the gate targets boot crashes, not health. + */ +export function evaluateBoot(httpStatus, body, expectedVersion) { + const failures = []; + if (httpStatus !== 200) failures.push(`health HTTP ${httpStatus} (expected 200)`); + if (!body || typeof body !== "object") failures.push("health body is not JSON"); + else if (body.version !== expectedVersion) + failures.push(`version "${body.version}" (expected "${expectedVersion}")`); + return { ok: failures.length === 0, failures }; +} + +/** Deterministic-enough free-ish port in a range CI runners don't use. */ +export function pickPort(seed = process.pid) { + return 23000 + (seed % 4000); +} + +function log(msg) { + console.log(`[pack-boot] ${msg}`); +} + +async function main() { + const ROOT = process.cwd(); + if (!fs.existsSync(path.join(ROOT, "dist", "server.js"))) { + console.error("[pack-boot] dist/server.js missing — run `npm run build:cli` first (this is a --with-build gate)"); + process.exit(2); + } + const expectedVersion = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version; + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pack-boot-")); + let child = null; + let exitCode = 1; + try { + log(`packing v${expectedVersion}…`); + const packOut = execFileSync("npm", ["pack", "--json", "--pack-destination", tmp], { + cwd: ROOT, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + const tarball = path.join(tmp, pickTarball(packOut)); + log(`installing ${path.basename(tarball)} into a clean prefix (postinstall runs for real)…`); + const prefix = path.join(tmp, "prefix"); + execFileSync("npm", ["install", "-g", "--prefix", prefix, tarball], { + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + + const port = pickPort(); + const dataDir = path.join(tmp, "data"); + fs.mkdirSync(dataDir, { recursive: true }); + const binPath = path.join(prefix, "bin", "omniroute"); + log(`booting installed CLI on :${port} (DATA_DIR isolated)…`); + child = spawn(binPath, ["serve", "--port", String(port)], { + env: { + ...process.env, + PORT: String(port), + DATA_DIR: dataDir, + JWT_SECRET: "pack-boot-smoke-secret-with-sufficient-length-000", + API_KEY_SECRET: "pack-boot-smoke-api-key-secret-long", + DISABLE_SQLITE_AUTO_BACKUP: "true", + OMNIROUTE_SKIP_SYSTEM_TRUST: "1", + }, + stdio: ["ignore", "pipe", "pipe"], + detached: true, + }); + const tail = []; + const keepTail = (chunk) => { + tail.push(String(chunk)); + while (tail.length > 80) tail.shift(); + }; + child.stdout.on("data", keepTail); + child.stderr.on("data", keepTail); + let childExit = null; + child.on("exit", (code) => { + childExit = code ?? -1; + }); + + const deadline = Date.now() + BOOT_DEADLINE_MS; + let verdict = { ok: false, failures: ["never polled"] }; + while (Date.now() < deadline) { + if (childExit !== null) { + verdict = { ok: false, failures: [`process exited with code ${childExit} before serving`] }; + break; + } + try { + const res = await fetch(`http://127.0.0.1:${port}/api/monitoring/health`); + const body = await res.json().catch(() => null); + verdict = evaluateBoot(res.status, body, expectedVersion); + if (verdict.ok) { + log(`healthy: HTTP 200, version ${body.version}, status "${body.status}"`); + break; + } + } catch { + // not listening yet — keep polling + } + await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); + } + + if (verdict.ok) { + log("✅ the packed tarball boots — #7065 class gate green"); + exitCode = 0; + } else { + console.error(`[pack-boot] ❌ boot FAILED: ${verdict.failures.join("; ")}`); + console.error("[pack-boot] last server output:\n" + tail.join("").split("\n").slice(-40).join("\n")); + exitCode = 1; + } + } finally { + if (child?.pid) { + try { + process.kill(-child.pid, "SIGTERM"); + } catch { + /* already gone */ + } + await new Promise((r) => setTimeout(r, 2_000)); + try { + process.kill(-child.pid, "SIGKILL"); + } catch { + /* already gone */ + } + } + fs.rmSync(tmp, { recursive: true, force: true }); + } + process.exit(exitCode); +} + +const isDirectRun = + process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isDirectRun) { + main().catch((e) => { + console.error("[pack-boot] fatal:", e.message); + process.exit(1); + }); +} diff --git a/scripts/check/check-test-discovery.mjs b/scripts/check/check-test-discovery.mjs index 33a2598ef8..e695ff252c 100644 --- a/scripts/check/check-test-discovery.mjs +++ b/scripts/check/check-test-discovery.mjs @@ -127,6 +127,13 @@ export const COLLECTORS = [ glob: "tests/e2e/protocol-clients.test.ts", sources: ["scripts/dev/run-protocol-clients-tests.mjs"], }, + // Playwright — suíte de homologação real (npm run homolog, L4 UI): run.mjs invoca + // `playwright test -c tests/homolog/ui/playwright.config.ts` (testMatch **/*.spec.ts). + { + glob: "tests/homolog/ui/*.spec.ts", + sources: ["scripts/homolog/run.mjs"], + anchors: { "scripts/homolog/run.mjs": "tests/homolog/ui/playwright.config.ts" }, + }, ]; const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); diff --git a/scripts/dev/main-server-timeouts.mjs b/scripts/dev/main-server-timeouts.mjs new file mode 100644 index 0000000000..a0019fca2d --- /dev/null +++ b/scripts/dev/main-server-timeouts.mjs @@ -0,0 +1,47 @@ +// Main-server keepAlive/headers timeouts (#7003) — SIBLING module of +// standalone-server-ws.mjs. The shipped server-ws.mjs may only import +// siblings copied next to it by assembleStandalone (peer-stamp, tls-options, +// the guards): a ../../src/... import resolves OUTSIDE the package after the +// copy to the dist root and crashes boot with ERR_MODULE_NOT_FOUND (caught +// live by check:pack-boot on 2026-07-15 — the #7065 class). +// Parity with src/shared/utils/runtimeTimeouts.ts#getMainServerTimeoutConfig +// is enforced by tests/unit/main-server-timeouts-parity.test.ts. + +export const DEFAULT_MAIN_SERVER_KEEPALIVE_TIMEOUT_MS = 65_000; +export const DEFAULT_MAIN_SERVER_HEADERS_TIMEOUT_MS = 66_000; + +function readTimeoutMs(env, name, defaultValue, { allowZero = false, logger } = {}) { + const raw = env[name]; + if (raw == null || raw.trim() === "") return defaultValue; + const parsed = Number(raw); + const isValid = Number.isFinite(parsed) && (allowZero ? parsed >= 0 : parsed > 0); + if (!isValid) { + logger?.(`Invalid ${name}="${raw}". Using default ${defaultValue}ms.`); + return defaultValue; + } + return Math.floor(parsed); +} + +export function getMainServerTimeoutConfig(env = process.env, logger) { + const keepAliveTimeoutMs = readTimeoutMs( + env, + "MAIN_SERVER_KEEPALIVE_TIMEOUT_MS", + DEFAULT_MAIN_SERVER_KEEPALIVE_TIMEOUT_MS, + { allowZero: true, logger } + ); + const headersTimeoutMs = readTimeoutMs( + env, + "MAIN_SERVER_HEADERS_TIMEOUT_MS", + DEFAULT_MAIN_SERVER_HEADERS_TIMEOUT_MS, + { allowZero: true, logger } + ); + return { + keepAliveTimeoutMs, + // Node requires headersTimeout > keepAliveTimeout; keep both configurable + // but always coherent (mirrors the canonical TS implementation). + headersTimeoutMs: + headersTimeoutMs > 0 && keepAliveTimeoutMs > 0 + ? Math.max(headersTimeoutMs, keepAliveTimeoutMs + 1_000) + : headersTimeoutMs, + }; +} diff --git a/scripts/dev/run-next.mjs b/scripts/dev/run-next.mjs index 68eb9d2198..54c33e56df 100644 --- a/scripts/dev/run-next.mjs +++ b/scripts/dev/run-next.mjs @@ -14,6 +14,7 @@ import headResponseGuard from "./head-response-guard.cjs"; import { ensureNativeSqlite } from "./ensure-native-sqlite.mjs"; import { isTurbopackCacheCorruption, purgeAllTurbopackCaches } from "./turbopackCacheHeal.mjs"; import { randomUUID } from "node:crypto"; +import { getMainServerTimeoutConfig } from "./main-server-timeouts.mjs"; const { maybeHandleDisallowedMethod } = methodGuard; const { wrapRequestListenerWithHeadResponseGuard } = headResponseGuard; @@ -153,6 +154,15 @@ async function start() { return requestHandler(req, res); }) ); + // Node's http.Server default keepAliveTimeout (5_000ms) races pooled + // keep-alive HTTP clients that idle longer than that between requests (e.g. + // the JVM java.net.http.HttpClient used by JetBrains AI Assistant), which + // reuse a socket the server already tore down and get 0 response bytes back + // (#7003). Raise both timeouts well above any realistic client idle-pool + // window, mirroring src/lib/apiBridgeServer.ts's pattern. + const mainServerTimeouts = getMainServerTimeoutConfig(); + server.keepAliveTimeout = mainServerTimeouts.keepAliveTimeoutMs; + server.headersTimeout = mainServerTimeouts.headersTimeoutMs; server.on("upgrade", async (req, socket, head) => { try { const responsesWsHandled = await responsesWsProxy.handleUpgrade(req, socket, head); diff --git a/scripts/dev/standalone-server-ws.mjs b/scripts/dev/standalone-server-ws.mjs index 3d7bd1f0ca..439a9c5171 100644 --- a/scripts/dev/standalone-server-ws.mjs +++ b/scripts/dev/standalone-server-ws.mjs @@ -7,6 +7,7 @@ import { maybeHandleWebdav } from "./webdav-handler.mjs"; import methodGuard from "./http-method-guard.cjs"; import headResponseGuard from "./head-response-guard.cjs"; import { resolveTlsOptions, createServerListener } from "./tls-options.mjs"; +import { getMainServerTimeoutConfig } from "./main-server-timeouts.mjs"; const originalCreateServer = http.createServer.bind(http); const proxiesByPort = new Map(); @@ -151,6 +152,19 @@ http.createServer = function createServerWithResponsesWs(...args) { // listener); otherwise the original http.Server. The downstream .on/.addListener // patches below apply identically to both (https.Server extends http.Server). const server = createServerListener(args, tlsOptions, { createHttp: originalCreateServer }); + // Node's http.Server default keepAliveTimeout (5_000ms) races pooled + // keep-alive HTTP clients that idle longer than that between requests (e.g. + // the JVM java.net.http.HttpClient used by JetBrains AI Assistant), which + // reuse a socket the server already tore down and get 0 response bytes back + // (#7003). This wrapper is what `omniroute serve` / Docker / Electron actually + // spawn in production (run-standalone.mjs prefers server-ws.mjs over the bare + // Next server.js), so it needs the same fix already wired into run-next.mjs + // (the dev-only entry point) — otherwise real installs never got it. Raise + // both timeouts well above any realistic client idle-pool window, mirroring + // src/lib/apiBridgeServer.ts's pattern. + const mainServerTimeouts = getMainServerTimeoutConfig(); + server.keepAliveTimeout = mainServerTimeouts.keepAliveTimeoutMs; + server.headersTimeout = mainServerTimeouts.headersTimeoutMs; const originalOn = server.on.bind(server); const originalAddListener = server.addListener.bind(server); diff --git a/scripts/homolog/gen-promptfoo.mjs b/scripts/homolog/gen-promptfoo.mjs new file mode 100644 index 0000000000..789f147076 --- /dev/null +++ b/scripts/homolog/gen-promptfoo.mjs @@ -0,0 +1,52 @@ +import fs from "node:fs"; +import path from "node:path"; +import { pickSmokeModels } from "./lib/providerTiers.mjs"; + +const baseUrl = process.env.HOMOLOG_BASE_URL; +const critical = (process.env.HOMOLOG_CRITICAL_PROVIDERS || "").split(",").filter(Boolean); + +const res = await fetch(`${baseUrl}/v1/models`, { + headers: { Authorization: `Bearer ${process.env.HOMOLOG_API_KEY}` }, +}); +if (!res.ok) throw new Error(`/v1/models HTTP ${res.status}`); +const catalog = (await res.json()).data; + +const picks = pickSmokeModels(catalog, critical); +const missing = picks.filter((p) => !p.model); +const providers = picks + .filter((p) => p.model) + .map((p) => ({ + id: `openai:chat:${p.model}`, + label: p.provider, + config: { + apiBaseUrl: `${baseUrl}/v1`, + apiKeyEnvar: "HOMOLOG_API_KEY", + max_tokens: 5, + temperature: 0, + // OmniRoute streama por default quando "stream" é omitido (streamDefaultMode + // legacy) — o parser JSON do promptfoo precisa da resposta non-stream. + passthrough: { stream: false, max_tokens: 5 }, + }, + })); + +const config = { + description: "OmniRoute homolog — smoke real 1 request/provider crítico", + prompts: ["Reply with exactly: OK"], + providers, + // O smoke valida o WIRING do provider (respondeu sem erro), não o comportamento + // do modelo: com max_tokens=5, modelos de reasoning podem gastar o budget antes + // de emitir o "OK" literal — icontains seria falso-positivo de quebra. + tests: [{ assert: [{ type: "javascript", value: "typeof output === 'string'" }] }], +}; +fs.mkdirSync("homolog-report/raw", { recursive: true }); +fs.writeFileSync( + path.join("homolog-report", "promptfooconfig.yaml"), + JSON.stringify(config, null, 2) // promptfoo aceita JSON como config YAML-compatível +); +fs.writeFileSync( + path.join("homolog-report", "raw", "provider-misses.json"), + JSON.stringify(missing, null, 2) +); +console.log( + `[gen-promptfoo] ${providers.length} providers no smoke, ${missing.length} misses de catálogo` +); diff --git a/scripts/homolog/lib/adminClient.mjs b/scripts/homolog/lib/adminClient.mjs new file mode 100644 index 0000000000..5f8c78e7cc --- /dev/null +++ b/scripts/homolog/lib/adminClient.mjs @@ -0,0 +1,65 @@ +// Cookie confirmado em src/app/api/auth/login/route.ts (cookieStore.set("auth_token", ...)) +// e em src/shared/utils/apiAuth.ts (isDashboardSessionAuthenticated lê "auth_token"). +const TOKEN_COOKIE = "auth_token"; + +export function extractJwtCookie(setCookies) { + for (const c of setCookies || []) { + const m = c.match(new RegExp(`^(${TOKEN_COOKIE}=[^;]+)`)); + if (m) return m[1]; + } + return null; +} + +export function extractApiKey(body) { + if (!body?.key || !body?.id) throw new Error("POST /api/keys sem key/id no corpo"); + return { key: body.key, id: body.id }; +} + +// fetch com 1 retry para erros de socket (keep-alive reciclado pelo servidor +// entre requests espaçados derruba o 1º write com EPIPE/other side closed). +async function fetchRetry(url, init, retries = 1) { + try { + return await fetch(url, init); + } catch (err) { + if (retries > 0) { + await new Promise((r) => setTimeout(r, 1_000)); + return fetchRetry(url, init, retries - 1); + } + throw err; + } +} + +/** Login admin → cria API key efêmera. Retorna {key, id, cookie, revoke()}. */ +export async function createEphemeralKey(baseUrl, password) { + const login = await fetchRetry(`${baseUrl}/api/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ password }), + }); + if (!login.ok) throw new Error(`login falhou: HTTP ${login.status}`); + const cookie = extractJwtCookie(login.headers.getSetCookie()); + if (!cookie) throw new Error("login sem cookie de sessão"); + + // sufixo único por run: dois runs paralelos (ou um cleanup por nome) nunca colidem + const name = `homolog-${new Date().toISOString().slice(0, 10)}-${Math.random().toString(36).slice(2, 8)}`; + const create = await fetchRetry(`${baseUrl}/api/keys`, { + method: "POST", + headers: { "Content-Type": "application/json", cookie }, + body: JSON.stringify({ name }), + }); + if (!create.ok) throw new Error(`criação de key falhou: HTTP ${create.status}`); + const { key, id } = extractApiKey(await create.json()); + + return { + key, + id, + cookie, + async revoke() { + const del = await fetchRetry(`${baseUrl}/api/keys/${id}`, { + method: "DELETE", + headers: { cookie }, + }); + if (!del.ok) throw new Error(`revogação da key ${id} falhou: HTTP ${del.status}`); + }, + }; +} diff --git a/scripts/homolog/lib/parity.mjs b/scripts/homolog/lib/parity.mjs new file mode 100644 index 0000000000..0fa00ea37c --- /dev/null +++ b/scripts/homolog/lib/parity.mjs @@ -0,0 +1,15 @@ +/** + * Avaliação pura de paridade do deploy (testável sem rede). + * @param {{status?: string, version?: string}} health corpo de /api/monitoring/health + * @param {{expectedVersion: string, httpStatus: number}} ctx + * @returns {{ok: boolean, failures: string[]}} + */ +export function evaluateParity(health, ctx) { + const failures = []; + if (ctx.httpStatus !== 200) failures.push(`health HTTP ${ctx.httpStatus} (esperado 200)`); + if (health?.status !== "healthy") + failures.push(`status "${health?.status}" (esperado "healthy")`); + if (health?.version !== ctx.expectedVersion) + failures.push(`version "${health?.version}" (esperado "${ctx.expectedVersion}")`); + return { ok: failures.length === 0, failures }; +} diff --git a/scripts/homolog/lib/promptfooToCtrf.mjs b/scripts/homolog/lib/promptfooToCtrf.mjs new file mode 100644 index 0000000000..06712f29ae --- /dev/null +++ b/scripts/homolog/lib/promptfooToCtrf.mjs @@ -0,0 +1,24 @@ +export function promptfooToCtrf(output) { + const rows = output?.results?.results || []; + const tests = rows.map((r) => ({ + name: `provider-smoke: ${r.provider?.label || r.provider?.id || "?"}`, + status: r.success ? "passed" : "failed", + duration: Math.round(r.latencyMs || 0), + ...(r.error ? { message: String(r.error).slice(0, 300) } : {}), + })); + const passed = tests.filter((t) => t.status === "passed").length; + return { + results: { + tool: { name: "promptfoo" }, + summary: { + tests: tests.length, + passed, + failed: tests.length - passed, + pending: 0, + skipped: 0, + other: 0, + }, + tests, + }, + }; +} diff --git a/scripts/homolog/lib/providerTiers.mjs b/scripts/homolog/lib/providerTiers.mjs new file mode 100644 index 0000000000..c10a8a0300 --- /dev/null +++ b/scripts/homolog/lib/providerTiers.mjs @@ -0,0 +1,7 @@ +/** Escolhe 1 modelo por provider crítico a partir do catálogo /v1/models. */ +export function pickSmokeModels(catalog, criticalProviders) { + return criticalProviders.map((provider) => { + const hit = catalog.find((m) => m.id.startsWith(`${provider}/`)); + return { provider, model: hit ? hit.id : null }; + }); +} diff --git a/scripts/homolog/lib/sseCheck.mjs b/scripts/homolog/lib/sseCheck.mjs new file mode 100644 index 0000000000..bc5ec2f90f --- /dev/null +++ b/scripts/homolog/lib/sseCheck.mjs @@ -0,0 +1,80 @@ +export function parseSseChunk(text) { + // Itera LINHAS dentro de cada bloco: a VPS emite comment-lines SSE + // (": x-omniroute-*") no mesmo bloco do "data: [DONE]", então olhar só o + // início do bloco perde o terminador. + const events = []; + for (const block of text.split(/\n\n/)) { + for (const line of block.split("\n")) { + const t = line.trim(); + if (t.startsWith("data:")) events.push(t.slice(5).trim()); + } + } + return events; +} + +export function summarizeStream(events) { + let contentDeltas = 0; + let done = false; + for (const e of events) { + if (e === "[DONE]") { + done = true; + continue; + } + try { + const j = JSON.parse(e); + if (j.choices?.[0]?.delta?.content) contentDeltas++; + } catch { + /* fragmento parcial — ignorado; o caller acumula buffer */ + } + } + const ok = contentDeltas >= 1 && done; + return { ok, contentDeltas, done }; +} + +/** Faz 1 chat streaming real e valida o protocolo SSE ponta-a-ponta. */ +export async function checkSse(baseUrl, apiKey, model, { retries = 1 } = {}) { + try { + const res = await fetch(`${baseUrl}/v1/chat/completions`, { + method: "POST", + headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, + body: JSON.stringify({ + model, + messages: [{ role: "user", content: "Reply with exactly: OK" }], + max_tokens: 5, + stream: true, + }), + }); + if (res.status !== 200) return { ok: false, failures: [`HTTP ${res.status}`] }; + const ct = res.headers.get("content-type") || ""; + if (!ct.includes("text/event-stream")) return { ok: false, failures: [`content-type "${ct}"`] }; + + const events = []; + let buffer = ""; + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + const deadline = Date.now() + 60_000; + while (Date.now() < deadline) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lastSep = buffer.lastIndexOf("\n\n"); + if (lastSep >= 0) { + events.push(...parseSseChunk(buffer.slice(0, lastSep + 2))); + buffer = buffer.slice(lastSep + 2); + } + } + // flush do resto do buffer (último bloco pode chegar sem "\n\n" no read final) + if (buffer.trim()) events.push(...parseSseChunk(buffer)); + const s = summarizeStream(events); + return { ok: s.ok, failures: s.ok ? [] : [`contentDeltas=${s.contentDeltas} done=${s.done}`] }; + } catch (err) { + // Socket keep-alive reciclado pelo servidor entre requests é transitório — + // 1 retry antes de reportar falha. Erro persistente é FALHA da camada, + // nunca crash do orquestrador. + if (retries > 0) { + await new Promise((r) => setTimeout(r, 1_000)); + return checkSse(baseUrl, apiKey, model, { retries: retries - 1 }); + } + return { ok: false, failures: [`fetch/stream error: ${err?.cause?.message || err.message}`] }; + } +} diff --git a/scripts/homolog/run.mjs b/scripts/homolog/run.mjs new file mode 100644 index 0000000000..f1b01be437 --- /dev/null +++ b/scripts/homolog/run.mjs @@ -0,0 +1,169 @@ +#!/usr/bin/env node +import { execSync, spawnSync } from "node:child_process"; +import fs from "node:fs"; +import { evaluateParity } from "./lib/parity.mjs"; +import { createEphemeralKey } from "./lib/adminClient.mjs"; +import { checkSse } from "./lib/sseCheck.mjs"; +import { promptfooToCtrf } from "./lib/promptfooToCtrf.mjs"; + +// ── env ────────────────────────────────────────────────────────────────── +if (fs.existsSync(".env.homolog")) { + for (const line of fs.readFileSync(".env.homolog", "utf8").split("\n")) { + const m = line.match(/^([A-Z_]+)=(.*)$/); + if (m && !process.env[m[1]]) process.env[m[1]] = m[2]; + } +} +const BASE = process.env.HOMOLOG_BASE_URL; +if (!BASE || !process.env.HOMOLOG_ADMIN_PASSWORD) { + console.error("Configure .env.homolog (HOMOLOG_BASE_URL, HOMOLOG_ADMIN_PASSWORD)"); + process.exit(2); +} +fs.rmSync("homolog-report", { recursive: true, force: true }); +// raw/ fica FORA do merge CTRF: `ctrf merge` tenta mesclar qualquer *.json com +// chave "results" e quebra no output cru do promptfoo. +fs.mkdirSync("homolog-report/raw", { recursive: true }); +const layers = []; // {name, ok, detail} +const record = (name, ok, detail = "") => { + layers.push({ name, ok, detail }); + console.log(`${ok ? "✅" : "❌"} ${name}${detail ? ` — ${detail}` : ""}`); +}; + +// ── L0 saúde/paridade ──────────────────────────────────────────────────── +const expectedVersion = + process.env.HOMOLOG_EXPECT_VERSION || JSON.parse(fs.readFileSync("package.json", "utf8")).version; +const healthRes = await fetch(`${BASE}/api/monitoring/health`); +const health = await healthRes.json().catch(() => ({})); +const parity = evaluateParity(health, { expectedVersion, httpStatus: healthRes.status }); +record("L0 saúde/paridade", parity.ok, parity.failures.join("; ")); +if (!parity.ok) { + console.error("Deploy divergente — abortando."); + writeSummary(layers, BASE, expectedVersion); + process.exit(1); +} + +// ── chave efêmera ──────────────────────────────────────────────────────── +const eph = await createEphemeralKey(BASE, process.env.HOMOLOG_ADMIN_PASSWORD); +process.env.HOMOLOG_API_KEY = eph.key; +try { + // modelo de smoke = 1º do tier crítico presente no catálogo + const models = ( + await ( + await fetch(`${BASE}/v1/models`, { headers: { Authorization: `Bearer ${eph.key}` } }) + ).json() + ).data; + const critical = (process.env.HOMOLOG_CRITICAL_PROVIDERS || "openai").split(","); + const smokeModel = + models.find((m) => critical.some((p) => m.id.startsWith(`${p}/`)))?.id || models[0].id; + + // ── L1 httpYac + SSE ─────────────────────────────────────────────────── + const hy = spawnSync( + "npx", + [ + "httpyac", + "send", + "tests/homolog/api/core.http", + "--all", + "--var", + `baseUrl=${BASE}`, + "--var", + `apiKey=${eph.key}`, + "--var", + `smokeModel=${smokeModel}`, + "--junit", + "--output", + "none", + ], + { encoding: "utf8" } + ); + fs.writeFileSync("homolog-report/httpyac-junit.xml", hy.stdout || ""); + record("L1 API (httpYac)", hy.status === 0); + const sse = await checkSse(BASE, eph.key, smokeModel); + record("L1 SSE streaming", sse.ok, (sse.failures || []).join("; ")); + + // ── L2 providers reais ───────────────────────────────────────────────── + try { + execSync("node scripts/homolog/gen-promptfoo.mjs", { stdio: "inherit", env: process.env }); + spawnSync( + "npx", + [ + "promptfoo", + "eval", + "-c", + "homolog-report/promptfooconfig.yaml", + "-o", + "homolog-report/raw/promptfoo.json", + "--no-cache", + ], + { encoding: "utf8", env: process.env } + ); + const pfOut = JSON.parse(fs.readFileSync("homolog-report/raw/promptfoo.json", "utf8")); + const pfCtrf = promptfooToCtrf(pfOut); + fs.writeFileSync("homolog-report/providers-ctrf.json", JSON.stringify(pfCtrf, null, 2)); + record( + "L2 providers reais", + pfCtrf.results.summary.failed === 0, + `${pfCtrf.results.summary.passed}/${pfCtrf.results.summary.tests} providers OK` + ); + } catch (err) { + // gerador/eval quebrando é falha da camada — o run continua para o L4 e o cleanup + record("L2 providers reais", false, err.message); + } + + // ── L4 UI ────────────────────────────────────────────────────────────── + const pw = spawnSync( + "npx", + ["playwright", "test", "-c", "tests/homolog/ui/playwright.config.ts"], + { + stdio: "inherit", + env: process.env, + } + ); + record("L4 UI (Playwright)", pw.status === 0); +} finally { + await eph + .revoke() + .then(() => record("cleanup: key efêmera revogada", true)) + .catch((e) => record("cleanup: key efêmera revogada", false, e.message)); +} + +// ── L5 relatório unificado ─────────────────────────────────────────────── +spawnSync( + "npx", + ["junit-to-ctrf", "homolog-report/httpyac-junit.xml", "-o", "homolog-report/api-ctrf.json"], + { + stdio: "inherit", + } +); +spawnSync( + "npx", + [ + "ctrf", + "merge", + "homolog-report", + "--output", + "homolog-ctrf.json", + "--output-dir", + "homolog-report", + ], + { + stdio: "inherit", + } +); + +writeSummary(layers, BASE, expectedVersion); +const failed = layers.filter((l) => !l.ok); +process.exit(failed.length ? 1 : 0); + +function writeSummary(rows, base, version) { + const md = [ + "# Homologação — relatório", + "", + `Alvo: ${base} · versão esperada: ${version}`, + "", + "| camada | resultado | detalhe |", + "|---|---|---|", + ...rows.map((l) => `| ${l.name} | ${l.ok ? "✅" : "❌"} | ${l.detail} |`), + ].join("\n"); + fs.writeFileSync("homolog-report/summary.md", md); + console.log(`\n${md}\n\nRelatório: homolog-report/ (CTRF unificado: homolog-ctrf.json)`); +} diff --git a/scripts/ops/runner-janitor.sh b/scripts/ops/runner-janitor.sh new file mode 100755 index 0000000000..99c081b10f --- /dev/null +++ b/scripts/ops/runner-janitor.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# runner-janitor — self-hosted runner box hygiene (WS3.3, v3.8.49 quality plan). +# +# The .113 runner box has recurring failure modes that until now were manual +# discipline: orphaned tmpfs/work dirs filling the disk, and >4 concurrent +# runners OOM-killing jobs (16 GB box; incidents on the v3.8.47 release day). +# Install via cron on the box (see docs/ops/RUNNER_BOX.md): +# */30 * * * * /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1 +# +# Exit codes: 0 healthy · 1 attention needed (printed to stdout for the log). +set -euo pipefail + +MAX_ACTIVE_RUNNERS="${MAX_ACTIVE_RUNNERS:-4}" +DISK_ALERT_PCT="${DISK_ALERT_PCT:-85}" +WORK_DIR_MAX_AGE_HOURS="${WORK_DIR_MAX_AGE_HOURS:-24}" +STATUS=0 + +echo "[janitor] $(date -u +%FT%TZ) start" + +# 1) Sweep stale runner temp/work leftovers (>24h — no legitimate job runs that long). +# Hardened for a root cron on world-writable paths: never follow a symlinked base +# (a compromised runner could plant one), -P + -xdev so the sweep cannot traverse +# out of the filesystem, and patterns narrowed to names OUR tooling creates +# (no generic tmp* — unrelated system temp files are out of scope). +for base in /tmp /home/*/actions-runner*/_work/_temp; do + [ -d "$base" ] || continue + [ -L "$base" ] && { echo "[janitor] skip symlinked base: $base"; continue; } + find -P "$base" -xdev -maxdepth 1 \( -name 'runner-*' -o -name 'omniroute-*' \) \ + ! -type l -mmin +$((WORK_DIR_MAX_AGE_HOURS * 60)) -exec rm -rf {} + 2>/dev/null || true +done +echo "[janitor] stale temp sweep done" + +# 2) Disk pressure — alert loudly before SQLITE_FULL kills jobs mid-run. +USAGE=$(df --output=pcent / | tail -1 | tr -dc '0-9') +if [ "$USAGE" -ge "$DISK_ALERT_PCT" ]; then + echo "[janitor] ⚠ ROOT DISK ${USAGE}% >= ${DISK_ALERT_PCT}% — clean before the next heavy run" + STATUS=1 +else + echo "[janitor] disk ${USAGE}% OK" +fi + +# 3) Concurrency ceiling — 8-wide OOMed the 16 GB box twice on release day; +# 4 is the proven ceiling. This CODIFIES the rule that was manual discipline. +ACTIVE=$(pgrep -fc "Runner.Listener" || true) +if [ "${ACTIVE:-0}" -gt "$MAX_ACTIVE_RUNNERS" ]; then + echo "[janitor] ⚠ ${ACTIVE} Runner.Listener processes > ceiling ${MAX_ACTIVE_RUNNERS} — stop the extra runners (systemctl stop actions.runner.)" + STATUS=1 +else + echo "[janitor] runners active: ${ACTIVE:-0}/${MAX_ACTIVE_RUNNERS} OK" +fi + +echo "[janitor] done status=$STATUS" +exit "$STATUS" diff --git a/scripts/quality/balance-e2e-shards.mjs b/scripts/quality/balance-e2e-shards.mjs new file mode 100644 index 0000000000..450a5438bb --- /dev/null +++ b/scripts/quality/balance-e2e-shards.mjs @@ -0,0 +1,99 @@ +#!/usr/bin/env node +/** + * balance-e2e-shards — duration-aware LPT bin-packing for the Playwright matrix (WS4.1). + * + * Playwright's --shard=N/M distributes by COUNT (per file with fullyParallel:false), + * blind to duration — measured skew on the 9-shard matrix: worst 24m47s vs best 1m47s + * (14×), putting E2E on the CI critical path. This script assigns spec FILES to shards + * by weight (Longest Processing Time greedy: heaviest first, always into the lightest + * shard) using config/quality/e2e-timings.json, and prints shard N's files (one per + * line) for `npx playwright test $FILES`. + * + * Safety: the union of all shards is verified to equal the discovered spec list — + * losing a spec silently would hollow the suite. Any inconsistency (or a missing + * timings file) exits non-zero so the CI step falls back to plain --shard=N/M. + * + * Weights are RELATIVE (unitless). The seed uses LOC as a proxy; regenerate from real + * durations by editing config/quality/e2e-timings.json (see its _meta note). + */ +import fs from "node:fs"; +import path from "node:path"; + +const E2E_DIR = path.join("tests", "e2e"); +const TIMINGS_PATH = path.join("config", "quality", "e2e-timings.json"); + +/** + * LPT greedy assignment. Deterministic: weight desc, then filename asc; ties on + * shard totals resolve to the lowest shard index. + * @param {{file: string, weight: number}[]} items + * @param {number} shardCount + * @returns {{files: string[], total: number}[]} + */ +export function lptAssign(items, shardCount) { + const shards = Array.from({ length: shardCount }, () => ({ files: [], total: 0 })); + const sorted = [...items].sort( + (a, b) => b.weight - a.weight || a.file.localeCompare(b.file) + ); + for (const item of sorted) { + let target = shards[0]; + for (const s of shards) if (s.total < target.total) target = s; + target.files.push(item.file); + target.total += item.weight; + } + return shards; +} + +/** + * Weight lookup with a median fallback so a NEW spec (no timing yet) lands mid-pack + * instead of skewing a shard. + * @param {string[]} files basenames + * @param {Record} timings + */ +export function weightItems(files, timings) { + const known = Object.entries(timings) + .filter(([k]) => !k.startsWith("_")) + .map(([, v]) => v) + .filter((v) => Number.isFinite(v) && v > 0) + .sort((a, b) => a - b); + const median = known.length ? known[Math.floor(known.length / 2)] : 1; + return files.map((file) => ({ + file, + weight: Number.isFinite(timings[file]) && timings[file] > 0 ? timings[file] : median, + })); +} + +function main() { + const shard = Number(process.argv[2]); + const total = Number(process.argv[3]); + if (!Number.isInteger(shard) || !Number.isInteger(total) || shard < 1 || shard > total) { + console.error("usage: node scripts/quality/balance-e2e-shards.mjs "); + process.exit(2); + } + if (!fs.existsSync(TIMINGS_PATH)) { + console.error(`[e2e-balance] ${TIMINGS_PATH} missing — caller should fall back to --shard`); + process.exit(3); + } + const timings = JSON.parse(fs.readFileSync(TIMINGS_PATH, "utf8")); + const files = fs + .readdirSync(E2E_DIR) + .filter((f) => f.endsWith(".spec.ts")) + .sort(); + if (!files.length) { + console.error(`[e2e-balance] no specs found under ${E2E_DIR}`); + process.exit(3); + } + const shards = lptAssign(weightItems(files, timings), total); + const assigned = shards.flatMap((s) => s.files).sort(); + if (assigned.length !== files.length || assigned.some((f, i) => f !== files[i])) { + console.error("[e2e-balance] INTERNAL: shard union != discovered specs — falling back"); + process.exit(3); + } + process.stdout.write( + shards[shard - 1].files.map((f) => path.join(E2E_DIR, f)).join("\n") + "\n" + ); +} + +const isDirectRun = + process.argv[1] && + path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isDirectRun) main(); diff --git a/scripts/quality/classify-pr-changes.mjs b/scripts/quality/classify-pr-changes.mjs index d1cff98f0f..b1ad58fd2a 100644 --- a/scripts/quality/classify-pr-changes.mjs +++ b/scripts/quality/classify-pr-changes.mjs @@ -17,19 +17,28 @@ import { fileURLToPath } from "node:url"; /** * @param {string[]} files relative paths from git diff - * @returns {{ code: boolean, docs: boolean, i18n: boolean, workflow: boolean }} + * @returns {{ code: boolean, docs: boolean, i18n: boolean, workflow: boolean, testsOnly: boolean }} */ export function classifyPaths(files) { let code = false; let docs = false; let i18n = false; let workflow = false; + // testsOnly (WS3.1 fast lane): every file lives under tests/ AND none is an e2e + // spec — such a diff cannot change the served app, so the E2E matrix may skip. + // Changing tests/e2e/** REQUIRES running e2e, so it is excluded from the shortcut. + let sawAnyFile = false; + let sawNonTest = false; + let sawE2eTest = false; for (const raw of files) { const f = String(raw || "") .trim() .replace(/\\/g, "/"); if (!f) continue; + sawAnyFile = true; + if (f.startsWith("tests/e2e/")) sawE2eTest = true; + else if (!f.startsWith("tests/")) sawNonTest = true; if (f.startsWith(".github/workflows/") || f === ".zizmor.yml") { workflow = true; @@ -84,7 +93,7 @@ export function classifyPaths(files) { code = true; } - return { code, docs, i18n, workflow }; + return { code, docs, i18n, workflow, testsOnly: sawAnyFile && !sawNonTest && !sawE2eTest }; } function main() { @@ -115,7 +124,7 @@ function main() { const c = classifyPaths(files); // GitHub Actions output format (also human-readable key=value). process.stdout.write( - `code=${c.code}\ndocs=${c.docs}\ni18n=${c.i18n}\nworkflow=${c.workflow}\n` + `code=${c.code}\ndocs=${c.docs}\ni18n=${c.i18n}\nworkflow=${c.workflow}\ntestsOnly=${c.testsOnly}\n` ); } diff --git a/scripts/quality/validate-release-green.mjs b/scripts/quality/validate-release-green.mjs index db0933ed67..13ffdb8ff0 100644 --- a/scripts/quality/validate-release-green.mjs +++ b/scripts/quality/validate-release-green.mjs @@ -543,6 +543,14 @@ async function main() { args: ["run", "check:pack-artifact"], timeout: 20 * 60 * 1000, }); + // WS1.2 (#7065 class): boot the REAL packed tarball from a clean install — + // the runtime gate structure checks cannot provide. Reuses the same dist/ build. + slow.push({ + id: "pack-boot", + label: "Tarball boot-smoke (installed CLI serves /health)", + args: ["run", "check:pack-boot"], + timeout: 15 * 60 * 1000, + }); } slow.forEach((g) => announce(`${g.label} [parallel]`)); const slowResults = await Promise.all( diff --git a/scripts/release/rehome-open-prs.mjs b/scripts/release/rehome-open-prs.mjs new file mode 100644 index 0000000000..934a0987b2 --- /dev/null +++ b/scripts/release/rehome-open-prs.mjs @@ -0,0 +1,158 @@ +#!/usr/bin/env node +// scripts/release/rehome-open-prs.mjs +// +// Parallel-cycle PR re-home (generate-release Phase 0a.0b, step 3). +// Retargets every open PR whose base is the FROZEN release/v onto the +// freshly cut release/v, so development keeps flowing while the captain +// owns the frozen branch. Design: _tasks/release-flow/2026-07-04_proposta-ciclo-paralelo-v2.md +// +// Usage: +// node scripts/release/rehome-open-prs.mjs [--dry-run] +// e.g. node scripts/release/rehome-open-prs.mjs 3.8.49 3.8.50 +// +// WHY THIS EXISTS AS A SCRIPT AND NOT A `gh pr edit` LOOP IN THE SKILL: +// +// 1. `gh pr edit --base` FAILS SILENTLY (v3.8.42 lesson). It exits 0 while +// leaving the base untouched — so every edit MUST be read back with +// `gh pr view --json baseRefName`. A hand-run loop skips that under +// fatigue; this does not. +// 2. Volume. At the v3.8.49 freeze there were 148 open PRs on the release +// branch — ~450 API calls between edit, verify and comment. That is not a +// thing a human does reliably at 2am mid-release. +// 3. `gh pr list` defaults to **30 results**. A loop written without +// `--limit` silently re-homes the first 30 and reports success. +// +// Idempotent: a PR already based on release/v is skipped, so a resumed +// release re-runs this safely. +// +// NOT covered here (by design): PRs opened AFTER this runs. Those are handled +// by flipping the repo's default_branch to release/v at 0a.0b — see the +// skill. Contributors open PRs against the default branch; if that still points +// at `main`, they never target a release branch at all. + +import { execFileSync } from "node:child_process"; +import { pathToFileURL } from "node:url"; + +const REPO = "diegosouzapw/OmniRoute"; + +function gh(args, { allowFail = false } = {}) { + try { + return execFileSync("gh", args, { encoding: "utf8" }).trim(); + } catch (err) { + if (allowFail) return null; + throw new Error(`gh ${args.join(" ")} failed: ${err.stderr || err.message}`); + } +} + +/** + * Pure: classify what should happen to a PR given its current base. + * Split out so the decision is unit-testable without touching the network. + */ +export function classify(pr, currentBase, nextBase) { + if (pr.baseRefName === nextBase) return { action: "skip", reason: "already re-homed" }; + if (pr.baseRefName !== currentBase) { + return { action: "skip", reason: `base is ${pr.baseRefName}, not the frozen branch` }; + } + if (pr.isDraft) return { action: "retarget", reason: "draft — retarget anyway, it still needs a home" }; + return { action: "retarget", reason: "open PR on the frozen branch" }; +} + +function main(argv) { + const dryRun = argv.includes("--dry-run"); + const [current, next] = argv.filter((a) => !a.startsWith("--")); + + if (!current || !next) { + console.error("Usage: node scripts/release/rehome-open-prs.mjs [--dry-run]"); + console.error(" e.g. node scripts/release/rehome-open-prs.mjs 3.8.49 3.8.50"); + process.exit(2); + } + + const currentBase = `release/v${current}`; + const nextBase = `release/v${next}`; + + // The next branch MUST exist before we point anything at it, or every edit + // 422s and we have re-homed nothing while reporting progress. + const exists = gh(["api", `repos/${REPO}/branches/${nextBase}`, "--jq", ".name"], { + allowFail: true, + }); + if (!exists) { + console.error(`✖ ${nextBase} does not exist on origin — cut it first (0a.0b step 1).`); + process.exit(1); + } + + // --limit 300: `gh pr list` returns 30 by default. Without this the loop + // silently re-homes a third of the queue and exits 0. + const raw = gh([ + "pr", "list", "--repo", REPO, "--state", "open", "--limit", "300", + "--base", currentBase, "--json", "number,title,isDraft,baseRefName", + ]); + const prs = JSON.parse(raw); + + console.log(`${prs.length} open PR(s) on ${currentBase} → ${nextBase}${dryRun ? " [DRY RUN]" : ""}\n`); + + const failed = []; + let moved = 0; + let skipped = 0; + + for (const pr of prs) { + const { action, reason } = classify(pr, currentBase, nextBase); + if (action === "skip") { + console.log(` · #${pr.number} skipped — ${reason}`); + skipped++; + continue; + } + if (dryRun) { + console.log(` → #${pr.number} would retarget — ${reason}`); + moved++; + continue; + } + + gh(["pr", "edit", String(pr.number), "--repo", REPO, "--base", nextBase], { allowFail: true }); + + // The read-back is the whole point: `gh pr edit --base` exits 0 on failure. + const actual = gh( + ["pr", "view", String(pr.number), "--repo", REPO, "--json", "baseRefName", "--jq", ".baseRefName"], + { allowFail: true } + ); + + if (actual !== nextBase) { + console.error(` ✖ #${pr.number} STILL on ${actual ?? "?"} — retarget did not take`); + failed.push({ number: pr.number, actual }); + continue; + } + + gh([ + "pr", "comment", String(pr.number), "--repo", REPO, + "--body", + `Re-homed to \`${nextBase}\`: v${current} entered its release freeze, so the branch now belongs ` + + `to the release captain and development continues on the next cycle. Nothing is wrong with this ` + + `PR — it just needed a live base. No action needed from you; CI will re-run against the new base.`, + ], { allowFail: true }); + + console.log(` ✔ #${pr.number} → ${nextBase}`); + moved++; + } + + console.log(`\n${moved} re-homed, ${skipped} skipped, ${failed.length} failed`); + + if (failed.length) { + console.error( + `\n✖ ${failed.length} PR(s) did not take the retarget: ${failed.map((f) => `#${f.number}`).join(", ")}\n` + + ` Re-run this script (it is idempotent) or retarget those by hand and verify with\n` + + ` gh pr view --json baseRefName` + ); + process.exit(1); + } + + if (!dryRun && moved > 0) { + console.log( + `\nReminder (0a.0b): flip the repo default_branch so PRs opened from now on are born on the\n` + + `right base — this script cannot reach PRs that do not exist yet:\n` + + ` gh api -X PATCH repos/${REPO} -f default_branch="${nextBase}"` + ); + } +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + main(process.argv.slice(2)); +} diff --git a/scripts/release/sync-next-cycle.mjs b/scripts/release/sync-next-cycle.mjs index 3a223baad3..ea2463cd99 100644 --- a/scripts/release/sync-next-cycle.mjs +++ b/scripts/release/sync-next-cycle.mjs @@ -106,6 +106,15 @@ function git(args, opts = {}) { return execFileSync("git", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, ...opts }).trim(); } +// The sync-back is the ONE write path to the release branch with no CI gate — a red +// merged tree pushed here turns the whole PR queue red (G1, v3.8.49 quality plan WS0.3). +// Returns the validate-release-green invocation to run before the push, or null when +// the operator passed --skip-green-gate (emergency hatch: tip reds verified pre-existing). +export function greenGateArgs(argv) { + if (argv.includes("--skip-green-gate")) return null; + return ["scripts/quality/validate-release-green.mjs", "--quick"]; +} + function main() { const NEXT = process.argv[2]; if (!/^\d+\.\d+\.\d+$/.test(NEXT || "")) { @@ -209,6 +218,29 @@ function main() { } git(["commit", "-m", `chore(release): sync main (v${prevVersion} close) into ${BRANCH} — parallel-cycle sync-back`], { cwd: WT }); + + // WS0.3 green gate: validate the MERGED tree before it reaches origin. The commit + // stays local on failure so the captain can inspect/fix in the sync worktree. + const gate = greenGateArgs(process.argv); + if (gate) { + const nm = path.join(WT, "node_modules"); + if (!fs.existsSync(nm)) fs.symlinkSync(path.join(ROOT, "node_modules"), nm, "dir"); + console.log("[sync-next-cycle] release-green --quick on the merged tree (pre-push gate)…"); + try { + execFileSync("node", gate, { cwd: WT, stdio: "inherit", maxBuffer: 64 * 1024 * 1024 }); + } catch { + console.error( + `[sync-next-cycle] ABORT: release-green --quick found HARD failures in the merged tree.` + + `\n The sync commit is LOCAL-ONLY in ${WT} — fix the reds there, then re-run this script.` + + `\n Use --skip-green-gate ONLY after verifying the reds pre-exist on origin/${BRANCH}.` + ); + process.exit(1); + } + fs.rmSync(nm, { force: true }); + } else { + console.warn("[sync-next-cycle] ⚠ --skip-green-gate: pushing WITHOUT release-green validation."); + } + git(["push", "origin", BRANCH], { cwd: WT }); const left = git(["rev-list", "--count", `${BRANCH}..origin/main`], { cwd: WT }); diff --git a/scripts/release/verify-published.mjs b/scripts/release/verify-published.mjs new file mode 100644 index 0000000000..cdab080daf --- /dev/null +++ b/scripts/release/verify-published.mjs @@ -0,0 +1,106 @@ +#!/usr/bin/env node +/** + * verify-published — post-publish net for the npm channel (WS1.4, #7065 class). + * + * After `npm stage approve` (or any publish), install the PUBLISHED version from the + * PUBLIC registry inside a clean `node:24-slim` container and boot it to a healthy + * /api/monitoring/health that reports the expected version. This is the last net: + * it validates the exact bytes users will install, on a machine with none of our + * repo/devbox state. Wired into /generate-release Phase 4 (monitoring). + * + * Usage: node scripts/release/verify-published.mjs + * Requires Docker (the clean container IS the point). Exit: 0 verified · + * 1 boot/version failure · 2 bad usage / docker unavailable. + */ +import { execFileSync, spawnSync } from "node:child_process"; + +const BOOT_DEADLINE_S = 240; +const PORT = 23987; + +/** Strict semver (with optional prerelease) — the version reaches a shell inside + * the container via env, but validate anyway (Hard Rule #13 defense in depth). */ +export function parseVersionArg(arg) { + if (!/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(arg || "")) return null; + return arg; +} + +/** docker invocation — version and knobs travel as env vars, never interpolated + * into the script body (Hard Rule #13). */ +export function buildDockerArgs(version) { + return [ + "run", + "--rm", + "-e", + `VERIFY_VERSION=${version}`, + "-e", + `VERIFY_PORT=${PORT}`, + "-e", + `VERIFY_DEADLINE_S=${BOOT_DEADLINE_S}`, + "node:24-slim", + "bash", + "-lc", + CONTAINER_SCRIPT, + ]; +} + +// Runs INSIDE node:24-slim. Reads everything from env; polls with node's fetch +// (slim has no curl). Kept as a single quoted constant — no runtime interpolation. +export const CONTAINER_SCRIPT = ` +set -euo pipefail +echo "[verify-published] npm i -g omniroute@\${VERIFY_VERSION} (public registry)" +npm install -g "omniroute@\${VERIFY_VERSION}" +export DATA_DIR=/tmp/omniroute-data JWT_SECRET=verify-published-secret-with-sufficient-length API_KEY_SECRET=verify-published-api-key-secret DISABLE_SQLITE_AUTO_BACKUP=true OMNIROUTE_SKIP_SYSTEM_TRUST=1 +mkdir -p "\$DATA_DIR" +omniroute serve --port "\$VERIFY_PORT" & +node -e ' +const port = process.env.VERIFY_PORT; +const want = process.env.VERIFY_VERSION; +const deadline = Date.now() + Number(process.env.VERIFY_DEADLINE_S) * 1000; +(async () => { + while (Date.now() < deadline) { + try { + const res = await fetch("http://127.0.0.1:" + port + "/api/monitoring/health"); + const body = await res.json().catch(() => null); + if (res.status === 200 && body && body.version === want) { + console.log("[verify-published] healthy: HTTP 200, version " + body.version); + process.exit(0); + } + if (res.status === 200 && body && body.version !== want) { + console.error("[verify-published] WRONG VERSION: " + body.version + " (want " + want + ")"); + process.exit(1); + } + } catch {} + await new Promise((r) => setTimeout(r, 2000)); + } + console.error("[verify-published] deadline: server never became healthy"); + process.exit(1); +})(); +' +`; + +function main() { + const version = parseVersionArg(process.argv[2]); + if (!version) { + console.error("usage: node scripts/release/verify-published.mjs [--no-docker]"); + process.exit(2); + } + try { + execFileSync("docker", ["--version"], { stdio: "ignore" }); + } catch { + console.error("[verify-published] docker unavailable — this verifier requires a clean container"); + process.exit(2); + } + console.log(`[verify-published] clean-container verify of omniroute@${version}…`); + const r = spawnSync("docker", buildDockerArgs(version), { stdio: "inherit" }); + if (r.status === 0) { + console.log("[verify-published] ✅ the published package installs and boots"); + process.exit(0); + } + console.error(`[verify-published] ❌ FAILED (exit ${r.status}) — consider: npm deprecate omniroute@${version} ""`); + process.exit(1); +} + +import path from "node:path"; +const isDirectRun = + process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isDirectRun) main(); diff --git a/skills/cli-skill-collector/SKILL.md b/skills/cli-skill-collector/SKILL.md index 2e3cec2776..add237ad11 100644 --- a/skills/cli-skill-collector/SKILL.md +++ b/skills/cli-skill-collector/SKILL.md @@ -1,7 +1,275 @@ --- name: cli-skill-collector -description: "Agent workflow: detect installed CLI coding tools (Claude Code, Codex, Cursor, Copilot, Cline, Hermes, OpenCode, etc.), search GitHub for matching agent skills, and install them to the detected tools. Replaces the standalone Skill Collector Python app." +description: "Detect installed CLI coding tools (Claude Code, Codex, Cursor, Copilot, Cline and more), search GitHub for matching agent skills, and install them to the detected tools via OmniRoute's built-in APIs." --- + + +## Overview + +Detect installed CLI coding tools (Claude Code, Codex, Cursor, Copilot, Cline and more), search GitHub for matching agent skills, and install them to the detected tools via OmniRoute's built-in APIs. + +## Quick install + +```bash +npm install -g omniroute # or: npx omniroute +omniroute --version +``` + +## Subcommands + +### `autostart` + +**Example:** + +```bash +omniroute autostart +``` + +### `autostart enable` + +**Example:** + +```bash +omniroute autostart enable +``` + +### `autostart disable` + +**Example:** + +```bash +omniroute autostart disable +``` + +### `autostart toggle` + +**Example:** + +```bash +omniroute autostart toggle +``` + +### `autostart status` + +**Example:** + +```bash +omniroute autostart status +``` + +### `config` + +Show or update CLI tool configuration + +**Example:** + +```bash +omniroute config +``` + +### `config list` + +List all CLI tools and config status + +**Flags:** + +- `--json` + +**Example:** + +```bash +omniroute config list +``` + +### `config get ` + +Show current config for a tool + +**Flags:** + +- `--json` + +**Example:** + +```bash +omniroute config get +``` + +### `config set ` + +Write config for a tool + +**Flags:** + +- `--model ` +- `--non-interactive` +- `--yes` + +**Example:** + +```bash +omniroute config set +``` + +### `config validate ` + +Validate config format without writing + +**Flags:** + +- `--model ` +- `--json` + +**Example:** + +```bash +omniroute config validate +``` + +### `config opencode` + +Generate OpenCode config (alias for + +**Flags:** + +- `--model ` +- `--non-interactive` +- `--yes` + +**Example:** + +```bash +omniroute config opencode +``` + +### `config lang` + +**Example:** + +```bash +omniroute config lang +``` + +### `config get` + +**Flags:** + +- `--json` + +**Example:** + +```bash +omniroute config get +``` + +### `config set ` + +**Flags:** + +- `--force` + +**Example:** + +```bash +omniroute config set +``` + +### `config list` + +**Flags:** + +- `--json` + +**Example:** + +```bash +omniroute config list +``` + +### `env` + +Show and manage environment variables + +**Example:** + +```bash +omniroute env +``` + +### `env show` + +Show current environment variables + +**Flags:** + +- `--json` + +**Example:** + +```bash +omniroute env show +``` + +### `env get ` + +Get a single environment variable + +**Example:** + +```bash +omniroute env get +``` + +### `env set ` + +Set an environment variable (current session only) + +**Example:** + +```bash +omniroute env set +``` + +### `setup` + +**Flags:** + +- `--password ` +- `--add-provider` +- `--provider ` +- `--provider-name ` +- `--api-key ` +- `--default-model ` +- `--provider-base-url ` +- `--test-provider` +- `--non-interactive` +- `--list` + +**Example:** + +```bash +omniroute setup +``` + +### `update` + +**Flags:** + +- `--check` +- `--apply` +- `--changelog` +- `--dry-run` +- `--no-backup` +- `--yes` + +**Example:** + +```bash +omniroute update +``` + + + # /cli-skill-collector — Agent Skill Collector @@ -150,3 +418,4 @@ fi - OmniRoute must be running locally on port 20128 (default) — see `docs/frameworks/SKILLS.md` for custom-port setups. - The `/api/skills/collect/*` and `/api/github-skills` endpoints require **management-scoped authentication** the same way every other `/api/skills/*` route does: a dashboard session, the loopback CLI token, or an API key with the `manage` scope (`requireManagementAuth()`). Auth is only bypassed when the server has no login/API-key requirement configured at all. - This replaces the standalone Skill Collector Python app — all logic is now inside OmniRoute. + diff --git a/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx index c7d356ee36..a16d5f9998 100644 --- a/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx @@ -47,6 +47,10 @@ export default function HermesAgentToolCard({ const [previewYaml, setPreviewYaml] = useState(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); const [firstSetupAt, setFirstSetupAt] = useState(null); + // Model aliases drive the passthrough provider groups (OpenRouter, Requesty, + // DGrid, AgentRouter, Charm Hyper, ...) in ModelSelectModal — without them, + // those providers never surface in the Hermes Agent role picker (#7151). + const [modelAliases, setModelAliases] = useState({}); // Track whether we have already seeded from batchStatus on this expand const seededFromBatchRef = useRef(false); @@ -109,8 +113,19 @@ export default function HermesAgentToolCard({ }); } loadCurrentConfig(); + fetchModelAliases(); }, [isExpanded, batchStatus, loadCurrentConfig]); + const fetchModelAliases = async () => { + try { + const res = await fetch("/api/models/alias"); + const data = await res.json(); + if (res.ok) setModelAliases(data.aliases || {}); + } catch (error) { + console.warn("Error fetching model aliases:", error); + } + }; + const setRoleSelection = (roleId: string, model: string, provider = "OmniRoute") => { setSelections((prev) => ({ ...prev, [roleId]: { model, provider } })); }; @@ -522,6 +537,7 @@ export default function HermesAgentToolCard({ showCombos={true} activeProviders={activeProviders} alwaysIncludeProviders={HERMES_AGENT_ZERO_CONFIG_PROVIDERS} + modelAliases={modelAliases} /> ); diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index a91ac0c7f3..6857fbd79a 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -14,6 +14,7 @@ import Toggle from "@/shared/components/Toggle"; import Tooltip from "@/shared/components/Tooltip"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { FieldLabelWithHelp, WeightTotalBar } from "./parts"; +import { useComboProxyAssignments } from "./useComboProxyAssignments"; import { ResponseValidationEditor, type ResponseValidationValue } from "./ResponseValidationEditor"; import ReasoningTokenBufferToggle from "./ReasoningTokenBufferToggle"; import { pickDisplayValue } from "@/shared/utils/maskEmail"; @@ -681,6 +682,7 @@ export default function CombosPage() { const notify = useNotificationStore(); const [proxyTargetCombo, setProxyTargetCombo] = useState(null); const [proxyConfig, setProxyConfig] = useState(null); + const { comboProxyAssignedIds, fetchComboProxyAssignments } = useComboProxyAssignments(); const [providerNodes, setProviderNodes] = useState([]); const [showUsageGuide, setShowUsageGuide] = useState(true); const [recentlyCreatedCombo, setRecentlyCreatedCombo] = useState(""); @@ -1210,7 +1212,7 @@ export default function CombosPage() { onTest={() => handleTestCombo(combo)} testing={testingCombo === combo.name} onProxy={() => setProxyTargetCombo(combo)} - hasProxy={!!proxyConfig?.combos?.[combo.id]} + hasProxy={comboProxyAssignedIds.has(combo.id) || !!proxyConfig?.combos?.[combo.id]} onToggle={() => handleToggleCombo(combo)} dragDisabled={savingComboOrder || activeFilter !== "all" || combos.length < 2} isDragged={comboDragIndex === index} @@ -1260,7 +1262,7 @@ export default function CombosPage() { {proxyTargetCombo && ( setProxyTargetCombo(null)} + onClose={() => (setProxyTargetCombo(null), fetchComboProxyAssignments())} level="combo" levelId={proxyTargetCombo.id} levelLabel={proxyTargetCombo.name} diff --git a/src/app/(dashboard)/dashboard/combos/useComboProxyAssignments.ts b/src/app/(dashboard)/dashboard/combos/useComboProxyAssignments.ts new file mode 100644 index 0000000000..93a9a0a5a0 --- /dev/null +++ b/src/app/(dashboard)/dashboard/combos/useComboProxyAssignments.ts @@ -0,0 +1,33 @@ +import { useCallback, useEffect, useState } from "react"; + +// #7149: the Combo "Set Proxy" modal writes through the modern proxy_assignments +// registry (scope="combo"), not the legacy /api/settings/proxy `combos` map — the +// dashboard's "has a proxy" indicator must read from the same registry the modal +// actually writes to, or it stays stale/gray even after a successful save. +export function parseComboProxyAssignmentIds(data: unknown): string[] { + const items = (data as { items?: unknown })?.items; + if (!Array.isArray(items)) return []; + return items + .filter( + (entry): entry is { scopeId: string; proxyId: string } => + !!(entry as { scopeId?: unknown })?.scopeId && !!(entry as { proxyId?: unknown })?.proxyId + ) + .map((entry) => entry.scopeId); +} + +export function useComboProxyAssignments() { + const [comboProxyAssignedIds, setComboProxyAssignedIds] = useState>(new Set()); + + const fetchComboProxyAssignments = useCallback(() => { + fetch("/api/settings/proxies/assignments?scope=combo") + .then((r) => (r.ok ? r.json() : null)) + .then((data) => setComboProxyAssignedIds(new Set(parseComboProxyAssignmentIds(data)))) + .catch(() => {}); + }, []); + + useEffect(() => { + fetchComboProxyAssignments(); + }, [fetchComboProxyAssignments]); + + return { comboProxyAssignedIds, fetchComboProxyAssignments }; +} diff --git a/src/app/(dashboard)/dashboard/search-tools/components/tabs/CompareTab.tsx b/src/app/(dashboard)/dashboard/search-tools/components/tabs/CompareTab.tsx index a9165a7684..8b702028cd 100644 --- a/src/app/(dashboard)/dashboard/search-tools/components/tabs/CompareTab.tsx +++ b/src/app/(dashboard)/dashboard/search-tools/components/tabs/CompareTab.tsx @@ -5,6 +5,8 @@ import { useTranslations } from "next-intl"; import Link from "next/link"; import type { SearchProviderCatalogItem } from "@/shared/schemas/searchTools"; +const MAX_COMPARE_PROVIDERS = 4; // D22: cap at 4 providers running in parallel + export interface CompareResult { provider: string; latency: number; @@ -69,12 +71,15 @@ export default function CompareTab({ providers, onMetrics }: CompareTabProps) { const toggleProvider = useCallback((id: string) => { setSelectedProviderIds((prev) => { if (prev.includes(id)) return prev.filter((p) => p !== id); + if (prev.length >= MAX_COMPARE_PROVIDERS) return prev; return [...prev, id]; }); }, []); const selectAll = useCallback(() => { - setSelectedProviderIds(activeSearchProviders.map((p) => p.id)); + setSelectedProviderIds( + activeSearchProviders.slice(0, MAX_COMPARE_PROVIDERS).map((p) => p.id) + ); }, [activeSearchProviders]); const clearAll = useCallback(() => { @@ -230,7 +235,7 @@ export default function CompareTab({ providers, onMetrics }: CompareTabProps) { - {/* Provider picker — no cap */} + {/* Provider picker — capped at MAX_COMPARE_PROVIDERS (D22) */}

@@ -253,9 +258,15 @@ export default function CompareTab({ providers, onMetrics }: CompareTabProps) {

+ {selectedProviderIds.length >= MAX_COMPARE_PROVIDERS && ( +

+ Maximum of {MAX_COMPARE_PROVIDERS} providers can be compared at once. +

+ )}
{activeSearchProviders.map((p) => { const selected = selectedProviderIds.includes(p.id); + const atCap = !selected && selectedProviderIds.length >= MAX_COMPARE_PROVIDERS; return ( . +setup("autentica e salva storageState", async ({ page }) => { + await page.goto("/login"); + await page.locator('input[type="password"]').fill(process.env.HOMOLOG_ADMIN_PASSWORD!); + await page.locator('button[type="submit"]').click(); + await page.waitForURL(/\/dashboard/); + await expect(page).toHaveURL(/dashboard/); + await page.context().storageState({ path: STORAGE_STATE }); +}); diff --git a/tests/homolog/ui/playwright.config.ts b/tests/homolog/ui/playwright.config.ts new file mode 100644 index 0000000000..78e099debe --- /dev/null +++ b/tests/homolog/ui/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig } from "@playwright/test"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +export const STORAGE_STATE = path.join(HERE, ".auth", "admin.json"); + +export default defineConfig({ + testDir: ".", + timeout: 60_000, + retries: 1, + // Sem fullyParallel, os 98 testes de routes.spec.ts (mesmo arquivo) rodam + // SERIALIZADOS num único worker (~10min); com ele, distribuem entre os workers. + fullyParallel: true, + workers: 8, + reporter: [ + ["list"], + [ + // outputDir ABSOLUTO: o reporter resolve paths relativos contra o CWD do + // processo (não contra o config) — um path relativo escapava do worktree. + "playwright-ctrf-json-reporter", + { + outputDir: path.resolve(HERE, "..", "..", "..", "homolog-report"), + outputFile: "ui-ctrf.json", + }, + ], + ], + use: { + baseURL: process.env.HOMOLOG_BASE_URL || "http://192.168.0.15:20128", + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, + projects: [ + { name: "setup", testMatch: /auth\.setup\.ts/ }, + { + name: "homolog", + testMatch: /.*\.spec\.ts/, + dependencies: ["setup"], + use: { storageState: STORAGE_STATE }, + }, + ], +}); diff --git a/tests/homolog/ui/routes.spec.ts b/tests/homolog/ui/routes.spec.ts new file mode 100644 index 0000000000..febc68749c --- /dev/null +++ b/tests/homolog/ui/routes.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from "@playwright/test"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// Descobre as rotas estáticas do dashboard a partir do próprio repo: +// cada page.tsx sob src/app/(dashboard)/dashboard vira uma rota; grupos (x) somem +// do path e rotas dinâmicas [param] são puladas (sem dado real garantido). +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); +const BASE = path.join(ROOT, "src", "app", "(dashboard)", "dashboard"); + +function discoverRoutes(dir: string, prefix = "/dashboard"): string[] { + const routes: string[] = []; + if (fs.existsSync(path.join(dir, "page.tsx"))) routes.push(prefix || "/"); + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + if (!e.isDirectory() || e.name.startsWith("[") || e.name.startsWith("_")) continue; + const seg = e.name.startsWith("(") ? "" : `/${e.name}`; + routes.push(...discoverRoutes(path.join(dir, e.name), `${prefix}${seg}`)); + } + return [...new Set(routes)]; +} + +for (const route of discoverRoutes(BASE)) { + test(`rota ${route} carrega sem crash`, async ({ page }) => { + const pageErrors: string[] = []; + page.on("pageerror", (err) => pageErrors.push(err.message)); + + const res = await page.goto(route, { waitUntil: "domcontentloaded" }); + expect(res!.status(), `HTTP em ${route}`).toBeLessThan(400); + // "networkidle" nunca assenta em telas com polling/websocket ao vivo (30s x 98 rotas + // estourava o run inteiro) — "load" + um settle curto e suficiente para hidratar e + // deixar um crash de client component (pageerror / error boundary) aparecer. + await page.waitForLoadState("load", { timeout: 10_000 }).catch(() => {}); + await page.waitForTimeout(1_500); + + // Error boundary do Next: nunca pode aparecer + await expect(page.locator("text=Application error")).toHaveCount(0); + expect(pageErrors, `pageerror em ${route}: ${pageErrors.join(" | ")}`).toHaveLength(0); + }); +} diff --git a/tests/integration/agent-skills-content.test.ts b/tests/integration/agent-skills-content.test.ts index 9e0ad388ab..74eed7741b 100644 --- a/tests/integration/agent-skills-content.test.ts +++ b/tests/integration/agent-skills-content.test.ts @@ -2,9 +2,9 @@ * Integration tests for Agent Skills content integrity. * * Verifies: - * 1. All 43 skill IDs from catalog have skills/{id}/ folder with SKILL.md. + * 1. All 44 skill IDs from catalog have skills/{id}/ folder with SKILL.md. * 2. Zero omniroute-* folders remain (post-prune: old omniroute-* skill dirs were removed). - * 3. 10 specific IDs have ... blocks: + * 3. 12 specific IDs have ... blocks: * omni-mcp, omni-compression, cli-providers, cli-eval, omni-agents-a2a, * omni-combos-routing, omni-auth, omni-resilience, omni-inference, cli-serve. * @@ -22,6 +22,7 @@ const ALL_IDS = [...API_SKILL_IDS, ...CLI_SKILL_IDS, ...CONFIG_SKILL_IDS] as str // IDs that must have a custom block const CUSTOM_BLOCK_IDS = [ + "cli-skill-collector", "omni-mcp", "omni-compression", "cli-providers", @@ -37,7 +38,7 @@ const CUSTOM_BLOCK_IDS = [ // ── §1: All 42 catalog IDs have skills/{id}/SKILL.md ───────────────────────── -test("all 43 catalog IDs have a skills/{id}/ directory", () => { +test("all 44 catalog IDs have a skills/{id}/ directory", () => { const missing: string[] = []; for (const id of ALL_IDS) { const dirPath = path.join(SKILLS_DIR, id); @@ -48,7 +49,7 @@ test("all 43 catalog IDs have a skills/{id}/ directory", () => { assert.deepEqual(missing, [], `Missing skill directories: ${missing.join(", ")}`); }); -test("all 43 catalog IDs have a skills/{id}/SKILL.md file", () => { +test("all 44 catalog IDs have a skills/{id}/SKILL.md file", () => { const missing: string[] = []; for (const id of ALL_IDS) { const skillPath = path.join(SKILLS_DIR, id, "SKILL.md"); @@ -113,7 +114,7 @@ for (const id of CUSTOM_BLOCK_IDS) { // ── Additional integrity checks ─────────────────────────────────────────────── -test("exactly 11 skills have custom blocks", () => { +test("exactly 12 skills have custom blocks", () => { const withCustomBlocks: string[] = []; for (const id of ALL_IDS) { const skillPath = path.join(SKILLS_DIR, id, "SKILL.md"); @@ -128,7 +129,7 @@ test("exactly 11 skills have custom blocks", () => { assert.deepEqual( withCustomBlocks.sort(), expectedIds, - `Expected exactly these 11 custom-block IDs: ${expectedIds.join(", ")}\nActual: ${withCustomBlocks.join(", ")}`, + `Expected exactly these 12 custom-block IDs: ${expectedIds.join(", ")}\nActual: ${withCustomBlocks.join(", ")}`, ); }); diff --git a/tests/integration/agent-skills-discovery.test.ts b/tests/integration/agent-skills-discovery.test.ts index 9e80ff4d18..6cc9a8dc52 100644 --- a/tests/integration/agent-skills-discovery.test.ts +++ b/tests/integration/agent-skills-discovery.test.ts @@ -69,8 +69,8 @@ test("every CLI skill ID has skills//SKILL.md on disk", () => { assert.deepEqual(missing, [], `Missing CLI SKILL.md files: ${missing.join(", ")}`); }); -test("total skill count is exactly 43 (23 API + 20 CLI)", () => { - assert.equal(API_SKILL_IDS.length + CLI_SKILL_IDS.length, 43); +test("total skill count is exactly 44 (23 API + 21 CLI)", () => { + assert.equal(API_SKILL_IDS.length + CLI_SKILL_IDS.length, 44); }); // ── §2: Frontmatter validation ──────────────────────────────────────────────── @@ -120,11 +120,11 @@ test("each SKILL.md body is at least 100 chars", () => { // ── §3: MCP tool omniroute_agent_skills_list ───────────────────────────────── -test("MCP omniroute_agent_skills_list handler returns count 44 (43 + config)", async () => { +test("MCP omniroute_agent_skills_list handler returns count 45 (44 + config)", async () => { const result = await agentSkillTools.omniroute_agent_skills_list.handler({}); - assert.equal(result.count, 44, `Expected 44 but got ${result.count}`); + assert.equal(result.count, 45, `Expected 45 but got ${result.count}`); assert.ok(Array.isArray(result.skills)); - assert.equal(result.skills.length, 44); + assert.equal(result.skills.length, 45); }); test("MCP omniroute_agent_skills_list result has all 42 IDs", async () => { @@ -157,9 +157,9 @@ test("A2A list-capabilities artifact content contains 42 skill IDs as table rows assert.ok(rows.length >= 42, `Expected at least 42 data rows but got ${rows.length}`); }); -test("A2A list-capabilities metadata.totalSkills === 44 (43 + config)", async () => { +test("A2A list-capabilities metadata.totalSkills === 45 (44 + config)", async () => { const result = await executeListCapabilities(stubTask); - assert.equal(result.metadata.totalSkills, 44); + assert.equal(result.metadata.totalSkills, 45); }); test("A2A list-capabilities artifact contains all 42 skill IDs", async () => { diff --git a/tests/integration/codex-chat-reasoning-http-e2e.test.ts b/tests/integration/codex-chat-reasoning-http-e2e.test.ts new file mode 100644 index 0000000000..e5a03749ff --- /dev/null +++ b/tests/integration/codex-chat-reasoning-http-e2e.test.ts @@ -0,0 +1,303 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import fsp from "node:fs/promises"; +import http from "node:http"; +import os from "node:os"; +import path from "node:path"; +import { once } from "node:events"; + +const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses"; +const ENCRYPTED_CONTENT_SENTINEL = "encrypted-codex-state:" + "A".repeat(910); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-chat-http-")); + +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.API_KEY_SECRET = "codex-chat-http-e2e-secret-123456"; +process.env.REQUIRE_API_KEY = "false"; +process.env.OMNIROUTE_LOG_REQUEST_SHAPE = "0"; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const chatRoute = await import("../../src/app/api/v1/chat/completions/route.ts"); + +const originalFetch = globalThis.fetch; + +type RecordedRequest = { + url: string; + method: string; + body: Record; +}; + +function responsesEvents() { + const response = { + id: "resp_reasoning_http", + object: "response", + status: "in_progress", + model: "gpt-5.6-sol", + output: [], + }; + return [ + { type: "response.created", response }, + { + type: "response.output_item.added", + output_index: 0, + item: { + id: "rs_reasoning_http", + type: "reasoning", + encrypted_content: ENCRYPTED_CONTENT_SENTINEL, + summary: [], + }, + }, + { + type: "response.output_item.done", + output_index: 0, + item: { + id: "rs_reasoning_http", + type: "reasoning", + encrypted_content: ENCRYPTED_CONTENT_SENTINEL, + summary: [], + }, + }, + { + type: "response.output_item.added", + output_index: 1, + item: { id: "msg_reasoning_http", type: "message", role: "assistant", content: [] }, + }, + { + type: "response.content_part.added", + item_id: "msg_reasoning_http", + output_index: 1, + content_index: 0, + part: { type: "output_text", text: "", annotations: [] }, + }, + { + type: "response.output_text.delta", + item_id: "msg_reasoning_http", + output_index: 1, + content_index: 0, + delta: "The answer is 42.", + }, + { + type: "response.output_text.done", + item_id: "msg_reasoning_http", + output_index: 1, + content_index: 0, + text: "The answer is 42.", + }, + { + type: "response.output_item.done", + output_index: 1, + item: { + id: "msg_reasoning_http", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "The answer is 42.", annotations: [] }], + }, + }, + { + type: "response.completed", + response: { + ...response, + status: "completed", + output: [ + { + id: "rs_reasoning_http", + type: "reasoning", + summary: [{ type: "summary_text", text: "I checked the contract. " }], + }, + { + id: "msg_reasoning_http", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "The answer is 42.", annotations: [] }], + }, + ], + usage: { input_tokens: 8, output_tokens: 9, total_tokens: 17 }, + }, + }, + ]; +} + +function mockResponsesSse() { + const nativeFraming = process.env.CODEX_NATIVE_EVENT_FRAMING === "1"; + return responsesEvents() + .map((event) => { + const eventLine = nativeFraming ? `event: ${event.type}\n` : ""; + return `${eventLine}data: ${JSON.stringify(event)}\n\n`; + }) + .join(""); +} + +async function readIncomingBody(request: http.IncomingMessage) { + const chunks: Buffer[] = []; + for await (const chunk of request) + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + return Buffer.concat(chunks); +} + +async function bridgeRouteResponse(response: Response, outgoing: http.ServerResponse) { + outgoing.writeHead(response.status, Object.fromEntries(response.headers.entries())); + if (!response.body) { + outgoing.end(); + return; + } + + const reader = response.body.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (!outgoing.write(value)) await once(outgoing, "drain"); + } + outgoing.end(); + } finally { + reader.releaseLock(); + } +} + +async function startRouteServer() { + const server = http.createServer(async (incoming, outgoing) => { + try { + if (incoming.method !== "POST" || incoming.url !== "/v1/chat/completions") { + outgoing.writeHead(404).end(); + return; + } + + const body = await readIncomingBody(incoming); + const address = server.address(); + assert(address && typeof address !== "string"); + const headers = new Headers(); + for (const [name, value] of Object.entries(incoming.headers)) { + if (Array.isArray(value)) value.forEach((item) => headers.append(name, item)); + else if (value !== undefined) headers.set(name, value); + } + const request = new Request(`http://127.0.0.1:${address.port}${incoming.url}`, { + method: incoming.method, + headers, + body, + }); + await bridgeRouteResponse(await chatRoute.POST(request), outgoing); + } catch (error) { + // Mock route bridge: surface the message, never the raw stack (js/stack-trace-exposure). + outgoing.writeHead(500, { "content-type": "text/plain" }); + outgoing.end(error instanceof Error ? error.message : String(error)); + } + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + assert(address && typeof address !== "string"); + return { server, url: `http://127.0.0.1:${address.port}/v1/chat/completions` }; +} + +function parseSse(raw: string) { + return raw + .split(/\n\n+/) + .map((block) => + block + .split("\n") + .find((line) => line.startsWith("data: ")) + ?.slice(6) + ) + .filter((data): data is string => Boolean(data)); +} + +async function closeServer(server: http.Server) { + if (!server.listening) return; + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); +} + +test("chat completions streams Codex Responses reasoning through real route HTTP", async () => { + const recorded: RecordedRequest[] = []; + let routeServer: http.Server | undefined; + + try { + await providersDb.createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "codex-http-reasoning", + email: "codex-http@example.test", + accessToken: "mock-codex-access-token", + refreshToken: "mock-codex-refresh-token", + tokenType: "Bearer", + expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + isActive: true, + testStatus: "active", + providerSpecificData: {}, + }); + + const routeHarness = await startRouteServer(); + routeServer = routeHarness.server; + + globalThis.fetch = async (input, init) => { + const request = input instanceof Request ? input : new Request(input, init); + if (request.url !== CODEX_RESPONSES_URL) { + throw new Error(`Unexpected external fetch in Codex HTTP test: ${request.url}`); + } + recorded.push({ + url: request.url, + method: request.method, + body: JSON.parse(await request.text()) as Record, + }); + return new Response(mockResponsesSse(), { + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + }); + }; + + const response = await originalFetch(routeHarness.url, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + model: "codex/gpt-5.6-sol", + stream: true, + reasoning_effort: "high", + messages: [{ role: "user", content: "What is the answer?" }], + }), + }); + const raw = await response.text(); + + assert.equal(response.status, 200, raw); + assert.match(response.headers.get("content-type") ?? "", /^text\/event-stream/); + assert.equal(recorded.length, 1); + assert.equal(recorded[0].url, CODEX_RESPONSES_URL); + assert.equal(recorded[0].method, "POST"); + assert.deepEqual(recorded[0].body.reasoning, { effort: "high", summary: "auto" }); + assert.deepEqual(recorded[0].body.input, [ + { + type: "message", + role: "user", + content: [{ type: "input_text", text: "What is the answer?" }], + }, + ]); + + const chunks = parseSse(raw); + assert.equal(chunks.at(-1), "[DONE]"); + const payloads = chunks.slice(0, -1).map((chunk) => JSON.parse(chunk)); + const reasoningContentDeltas = payloads + .map((payload) => payload.choices?.[0]?.delta?.reasoning_content) + .filter((content): content is string => Boolean(content)); + assert.equal(reasoningContentDeltas.length, 1); + const reasoningContent = reasoningContentDeltas.join(""); + assert.match(reasoningContent, /encrypted (?:state|private reasoning)/i); + assert(!raw.includes(ENCRYPTED_CONTENT_SENTINEL), raw); + assert(!reasoningContent.includes(ENCRYPTED_CONTENT_SENTINEL), reasoningContent); + assert( + payloads.some((payload) => payload.choices?.[0]?.delta?.content === "The answer is 42.") + ); + assert(!raw.includes("response.reasoning_summary_text.delta"), raw); + assert(!raw.includes('"type":"error"'), raw); + assert(!raw.includes('"error"'), raw); + } finally { + globalThis.fetch = originalFetch; + if (routeServer) await closeServer(routeServer); + core.closeDbInstance({ checkpointMode: null }); + await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true }); + } +}); diff --git a/tests/unit/agent-bridge-dns-toggle-method-7157.test.ts b/tests/unit/agent-bridge-dns-toggle-method-7157.test.ts new file mode 100644 index 0000000000..53a7c4311a --- /dev/null +++ b/tests/unit/agent-bridge-dns-toggle-method-7157.test.ts @@ -0,0 +1,24 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const clientPath = path.resolve( + __dirname, + "../../src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx" +); +const source = readFileSync(clientPath, "utf8"); + +test("#7157: dns toggle fetch call uses method POST (route.ts only exports POST)", () => { + const dnsCallMatch = source.match( + /\/api\/tools\/agent-bridge\/agents\/\$\{agentId\}\/dns`,\s*\{\s*method:\s*"([A-Z]+)"/ + ); + assert.ok(dnsCallMatch, "expected to find the dns fetch call in AgentBridgePageClient.tsx"); + assert.equal( + dnsCallMatch?.[1], + "POST", + "dns fetch call must use method: 'POST' to match the route.ts export (issue #7157)" + ); +}); diff --git a/tests/unit/agentSkillTools-mcp.test.ts b/tests/unit/agentSkillTools-mcp.test.ts index a34acea595..e344f14928 100644 --- a/tests/unit/agentSkillTools-mcp.test.ts +++ b/tests/unit/agentSkillTools-mcp.test.ts @@ -58,11 +58,11 @@ test("each agentSkillTool has name, description, inputSchema, and handler", () = // ─── omniroute_agent_skills_list ──────────────────────────────────────────── -test("omniroute_agent_skills_list with no filters returns all 44 skills", async () => { +test("omniroute_agent_skills_list with no filters returns all 45 skills", async () => { const result = await agentSkillTools.omniroute_agent_skills_list.handler({}); - assert.equal(result.count, 44, `Expected 44 but got ${result.count}`); + assert.equal(result.count, 45, `Expected 45 but got ${result.count}`); assert.ok(Array.isArray(result.skills)); - assert.equal(result.skills.length, 44); + assert.equal(result.skills.length, 45); }); test("omniroute_agent_skills_list({category:'api'}) returns exactly 23 entries", async () => { @@ -71,9 +71,9 @@ test("omniroute_agent_skills_list({category:'api'}) returns exactly 23 entries", assert.ok(result.skills.every((s: { category: string }) => s.category === "api")); }); -test("omniroute_agent_skills_list({category:'cli'}) returns exactly 20 entries", async () => { +test("omniroute_agent_skills_list({category:'cli'}) returns exactly 21 entries", async () => { const result = await agentSkillTools.omniroute_agent_skills_list.handler({ category: "cli" }); - assert.equal(result.count, 20, `Expected 20 cli skills but got ${result.count}`); + assert.equal(result.count, 21, `Expected 21 cli skills but got ${result.count}`); assert.ok(result.skills.every((s: { category: string }) => s.category === "cli")); }); @@ -83,7 +83,7 @@ test("omniroute_agent_skills_list result includes coverage shape", async () => { assert.ok(typeof result.coverage.api === "object"); assert.ok(typeof result.coverage.cli === "object"); assert.equal(result.coverage.api.total, 23); - assert.equal(result.coverage.cli.total, 20); + assert.equal(result.coverage.cli.total, 21); assert.ok(typeof result.coverage.totalSkills === "number"); assert.ok(typeof result.coverage.generatedAt === "string"); }); @@ -168,11 +168,11 @@ test("omniroute_agent_skills_coverage({}) returns coverage shape", async () => { assert.ok(typeof result.api === "object"); assert.ok(typeof result.cli === "object"); assert.equal(result.api.total, 23); - assert.equal(result.cli.total, 20); + assert.equal(result.cli.total, 21); assert.ok(typeof result.api.have === "number"); assert.ok(typeof result.cli.have === "number"); assert.ok(result.api.have >= 0 && result.api.have <= 23); - assert.ok(result.cli.have >= 0 && result.cli.have <= 20); + assert.ok(result.cli.have >= 0 && result.cli.have <= 21); assert.ok(typeof result.totalSkills === "number"); assert.equal(result.totalSkills, result.api.have + result.cli.have + (result.config?.have ?? 0)); assert.ok(typeof result.generatedAt === "string"); diff --git a/tests/unit/agentSkills-catalog.test.ts b/tests/unit/agentSkills-catalog.test.ts index 9820c627c7..eec31d49ad 100644 --- a/tests/unit/agentSkills-catalog.test.ts +++ b/tests/unit/agentSkills-catalog.test.ts @@ -15,10 +15,10 @@ const agentSkillsConstants = await import("../../src/shared/constants/agentSkill // ─── Counts ─────────────────────────────────────────────────────────────────── -test("getCatalog() returns exactly 44 entries", () => { +test("getCatalog() returns exactly 45 entries", () => { refreshCatalog(); const catalog = getCatalog(); - assert.equal(catalog.length, 44, `Expected 44 but got ${catalog.length}`); + assert.equal(catalog.length, 45, `Expected 45 but got ${catalog.length}`); }); test("API_SKILL_IDS has exactly 23 entries", () => { @@ -26,7 +26,7 @@ test("API_SKILL_IDS has exactly 23 entries", () => { }); test("CLI_SKILL_IDS has exactly 20 entries", () => { - assert.equal(CLI_SKILL_IDS.length, 20); + assert.equal(CLI_SKILL_IDS.length, 21); }); test("getCatalog() contains exactly 22 api skills", () => { @@ -34,9 +34,9 @@ test("getCatalog() contains exactly 22 api skills", () => { assert.equal(apiSkills.length, 23); }); -test("getCatalog() contains exactly 20 cli skills", () => { +test("getCatalog() contains exactly 21 cli skills", () => { const cliSkills = getCatalog().filter((s) => s.category === "cli"); - assert.equal(cliSkills.length, 20); + assert.equal(cliSkills.length, 21); }); // ─── ID format ──────────────────────────────────────────────────────────────── @@ -160,9 +160,9 @@ test("filterCatalog({ category: 'api' }) returns 23 api skills", () => { } }); -test("filterCatalog({ category: 'cli' }) returns 20 cli skills", () => { +test("filterCatalog({ category: 'cli' }) returns 21 cli skills", () => { const skills = filterCatalog({ category: "cli" }); - assert.equal(skills.length, 20); + assert.equal(skills.length, 21); for (const s of skills) { assert.equal(s.category, "cli"); } @@ -185,9 +185,9 @@ test("filterCatalog({ area: 'nonexistent' }) returns empty array", () => { assert.equal(skills.length, 0); }); -test("filterCatalog({}) returns full catalog (44 entries)", () => { +test("filterCatalog({}) returns full catalog (45 entries)", () => { const skills = filterCatalog({}); - assert.equal(skills.length, 44); + assert.equal(skills.length, 45); }); // ─── refreshCatalog ─────────────────────────────────────────────────────────── @@ -214,9 +214,9 @@ test("computeCoverage() returns valid SkillCoverage shape", () => { assert.ok(cov.api.have >= 0 && cov.api.have <= 23); assert.ok(typeof cov.cli === "object"); - assert.equal(cov.cli.total, 20); + assert.equal(cov.cli.total, 21); assert.ok(typeof cov.cli.have === "number"); - assert.ok(cov.cli.have >= 0 && cov.cli.have <= 20); + assert.ok(cov.cli.have >= 0 && cov.cli.have <= 21); assert.equal(cov.totalSkills, cov.api.have + cov.cli.have + (cov.config?.have ?? 0)); @@ -255,6 +255,6 @@ test("CLI_SKILL_IDS first entry is cli-serve", () => { assert.equal(CLI_SKILL_IDS[0], "cli-serve"); }); -test("CLI_SKILL_IDS last entry is cli-setup", () => { - assert.equal(CLI_SKILL_IDS[CLI_SKILL_IDS.length - 1], "cli-setup"); +test("CLI_SKILL_IDS last entry is cli-skill-collector", () => { + assert.equal(CLI_SKILL_IDS[CLI_SKILL_IDS.length - 1], "cli-skill-collector"); }); diff --git a/tests/unit/agentSkills-generator.test.ts b/tests/unit/agentSkills-generator.test.ts index db54f73ad2..95475887d9 100644 --- a/tests/unit/agentSkills-generator.test.ts +++ b/tests/unit/agentSkills-generator.test.ts @@ -61,11 +61,11 @@ test("dry-run (default) returns report without writing any files", async () => { outputDir: tmpDir, }); - // All 44 skills should appear as generated (would-write) since dir is empty + // All 45 skills should appear as generated (would-write) since dir is empty assert.equal( report.generated.length + report.unchanged.length, - 44, - `Expected 44 total (generated+unchanged), got generated=${report.generated.length} unchanged=${report.unchanged.length}`, + 45, + `Expected 45 total (generated+unchanged), got generated=${report.generated.length} unchanged=${report.unchanged.length}`, ); assert.equal(report.errors.length, 0, `Unexpected errors: ${JSON.stringify(report.errors)}`); @@ -81,7 +81,7 @@ test("dry-run (default) returns report without writing any files", async () => { } }); -test("dry-run generates report with 44 total (generated+unchanged)", async () => { +test("dry-run generates report with 45 total (generated+unchanged)", async () => { const tmpDir = mkTmpDir(); try { refreshCatalog(); @@ -91,7 +91,7 @@ test("dry-run generates report with 44 total (generated+unchanged)", async () => outputDir: tmpDir, }); const total = report.generated.length + report.unchanged.length; - assert.equal(total, 44); + assert.equal(total, 45); } finally { rmTmpDir(tmpDir); } @@ -134,7 +134,7 @@ test("apply mode writes SKILL.md with valid frontmatter for omni-providers", asy } }); -test("apply mode writes all 44 SKILL.md files when no onlyIds filter", async () => { +test("apply mode writes all 45 SKILL.md files when no onlyIds filter", async () => { const tmpDir = mkTmpDir(); try { refreshCatalog(); @@ -145,7 +145,7 @@ test("apply mode writes all 44 SKILL.md files when no onlyIds filter", async () }); assert.equal(report.errors.length, 0, `Errors: ${JSON.stringify(report.errors)}`); - assert.equal(report.generated.length, 44); + assert.equal(report.generated.length, 45); // Verify all dirs exist const catalog = getCatalog(); diff --git a/tests/unit/agentSkills-routes.test.ts b/tests/unit/agentSkills-routes.test.ts index 16265f4a4f..1de6eccf2d 100644 --- a/tests/unit/agentSkills-routes.test.ts +++ b/tests/unit/agentSkills-routes.test.ts @@ -101,15 +101,15 @@ test.after(() => { // GET /api/agent-skills // ═════════════════════════════════════════════════════════════════════════════ -test("GET /api/agent-skills — returns 44 skills with count and coverage", async () => { +test("GET /api/agent-skills — returns 45 skills with count and coverage", async () => { const req = makeRequest("GET", "http://localhost/api/agent-skills"); const res = await listRoute.GET(req); assert.equal(res.status, 200); const body = (await res.json()) as { skills: unknown[]; count: number; coverage: unknown }; - assert.equal(body.count, 44, `Expected 44 skills but got ${body.count}`); + assert.equal(body.count, 45, `Expected 45 skills but got ${body.count}`); assert.equal(Array.isArray(body.skills), true); - assert.equal(body.skills.length, 44); + assert.equal(body.skills.length, 45); assert.ok(body.coverage !== undefined, "coverage should be present"); }); @@ -123,13 +123,13 @@ test("GET /api/agent-skills?category=api — returns 23 api skills", async () => assert.ok(body.skills.every((s) => s.category === "api"), "All skills should be api category"); }); -test("GET /api/agent-skills?category=cli — returns 20 cli skills", async () => { +test("GET /api/agent-skills?category=cli — returns 21 cli skills", async () => { const req = makeRequest("GET", "http://localhost/api/agent-skills?category=cli"); const res = await listRoute.GET(req); assert.equal(res.status, 200); const body = (await res.json()) as { skills: Array<{ category: string }>; count: number }; - assert.equal(body.count, 20); + assert.equal(body.count, 21); assert.ok(body.skills.every((s) => s.category === "cli"), "All skills should be cli category"); }); @@ -268,7 +268,7 @@ test("GET /api/agent-skills/coverage — returns valid SkillCoverage shape", asy }; assert.equal(body.api.total, 23, "api.total must be 23"); - assert.equal(body.cli.total, 20, "cli.total must be 20"); + assert.equal(body.cli.total, 21, "cli.total must be 21"); assert.ok(typeof body.totalSkills === "number", "totalSkills must be a number"); assert.ok(typeof body.generatedAt === "string", "generatedAt must be a string"); // generatedAt must be a valid ISO datetime diff --git a/tests/unit/balance-e2e-shards.test.ts b/tests/unit/balance-e2e-shards.test.ts new file mode 100644 index 0000000000..6484dfccb2 --- /dev/null +++ b/tests/unit/balance-e2e-shards.test.ts @@ -0,0 +1,75 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { lptAssign, weightItems } from "../../scripts/quality/balance-e2e-shards.mjs"; + +// WS4.1 (v3.8.49 quality plan) — the E2E matrix skew was 14× (24m47s vs 1m47s) +// because Playwright --shard distributes by count, not duration. These tests pin +// the LPT packing invariants; the hard one is COMPLETENESS (a lost spec would +// silently hollow the suite — the CLI self-checks it and falls back to --shard). + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); + +test("lptAssign puts the heaviest item alone before doubling up lighter shards", () => { + const shards = lptAssign( + [ + { file: "huge.spec.ts", weight: 100 }, + { file: "a.spec.ts", weight: 30 }, + { file: "b.spec.ts", weight: 30 }, + { file: "c.spec.ts", weight: 30 }, + ], + 2 + ); + assert.deepEqual(shards[0].files, ["huge.spec.ts"]); + assert.deepEqual(shards[1].files, ["a.spec.ts", "b.spec.ts", "c.spec.ts"]); + assert.equal(shards[0].total, 100); + assert.equal(shards[1].total, 90); +}); + +test("lptAssign is deterministic on equal weights (filename tiebreak)", () => { + const items = [ + { file: "b.spec.ts", weight: 10 }, + { file: "a.spec.ts", weight: 10 }, + ]; + const s1 = lptAssign(items, 2); + const s2 = lptAssign([...items].reverse(), 2); + assert.deepEqual(s1, s2); +}); + +test("completeness: every file lands in exactly one shard", () => { + const items = Array.from({ length: 37 }, (_, i) => ({ + file: `f${String(i).padStart(2, "0")}.spec.ts`, + weight: (i * 7) % 40, + })); + const shards = lptAssign(items, 9); + const union = shards.flatMap((s) => s.files).sort(); + assert.deepEqual(union, items.map((i) => i.file).sort()); +}); + +test("weightItems gives unknown/new specs the median weight, not an extreme", () => { + const items = weightItems(["new.spec.ts", "big.spec.ts", "small.spec.ts"], { + _meta: "x", + "big.spec.ts": 600, + "small.spec.ts": 20, + "other.spec.ts": 100, + }); + const byFile = Object.fromEntries(items.map((i) => [i.file, i.weight])); + assert.equal(byFile["big.spec.ts"], 600); + assert.equal(byFile["small.spec.ts"], 20); + assert.equal(byFile["new.spec.ts"], 100); // median of [20,100,600] +}); + +test("the committed timings seed covers every current e2e spec (no drift)", () => { + const timings = JSON.parse( + fs.readFileSync(path.join(ROOT, "config", "quality", "e2e-timings.json"), "utf8") + ); + const specs = fs + .readdirSync(path.join(ROOT, "tests", "e2e")) + .filter((f) => f.endsWith(".spec.ts")); + const missing = specs.filter((f) => !(f in timings)); + // Missing entries are tolerated at runtime (median fallback) — this assert keeps + // the seed honest so balance quality does not silently rot as specs are added. + assert.deepEqual(missing, [], `add to config/quality/e2e-timings.json: ${missing.join(", ")}`); +}); diff --git a/tests/unit/build/check-dashboard-typecheck.test.ts b/tests/unit/build/check-dashboard-typecheck.test.ts new file mode 100644 index 0000000000..dec6d54fad --- /dev/null +++ b/tests/unit/build/check-dashboard-typecheck.test.ts @@ -0,0 +1,111 @@ +// tests/unit/build/check-dashboard-typecheck.test.ts +// Unit tests for the pure parsing/diff helpers in check-dashboard-typecheck.mjs. +// No child process is spawned — synthetic tsc-style output only, so the suite is +// fast and hermetic. Proves the gate actually DETECTS the #6625/#6909 bug class +// (an orphaned identifier — used but not declared — in a dashboard TSX file), +// not just that the script runs. + +import test from "node:test"; +import assert from "node:assert/strict"; +import { + parseTscOutput, + diffAgainstBaseline, +} from "../../../scripts/check/check-dashboard-typecheck.mjs"; + +test("parseTscOutput: parses a TS2304 orphaned-identifier error (the #6625/#6909 bug class)", () => { + const raw = + `src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx(564,7): error TS2304: Cannot find name 'setPoolLoaded'.\n` + + `src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx(1204,12): error TS2304: Cannot find name 'poolLoaded'.\n`; + + const counts = parseTscOutput(raw); + + assert.deepEqual(counts, { + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": { + TS2304: 2, + }, + }); +}); + +test("parseTscOutput: ignores non-error lines (summary/info output)", () => { + const raw = + `Some info line that is not an error\n` + + `src/app/(dashboard)/dashboard/foo.tsx(1,1): error TS2339: Property 'bar' does not exist.\n` + + `Found 1 error in 1 file.\n`; + + const counts = parseTscOutput(raw); + + assert.deepEqual(counts, { + "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 1 }, + }); +}); + +test("parseTscOutput: returns empty map for clean output", () => { + assert.deepEqual(parseTscOutput(""), {}); + assert.deepEqual(parseTscOutput("Found 0 errors.\n"), {}); +}); + +test("diffAgainstBaseline: flags a brand-new orphaned-identifier error as a regression", () => { + const baseline = {}; + const live = { + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": { + TS2304: 5, + }, + }; + + const { regressions, improvements } = diffAgainstBaseline(live, baseline); + + assert.equal(regressions.length, 1); + assert.equal( + regressions[0].file, + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx" + ); + assert.equal(regressions[0].code, "TS2304"); + assert.equal(regressions[0].liveCount, 5); + assert.equal(regressions[0].baselineCount, 0); + assert.equal(improvements.length, 0); +}); + +test("diffAgainstBaseline: does NOT flag a frozen pre-existing error within its baselined count", () => { + const baseline = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 3 } }; + const live = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 3 } }; + + const { regressions, improvements } = diffAgainstBaseline(live, baseline); + + assert.equal(regressions.length, 0); + assert.equal(improvements.length, 0); +}); + +test("diffAgainstBaseline: flags a count INCREASE beyond the frozen baseline as a regression", () => { + const baseline = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 2 } }; + const live = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 3 } }; + + const { regressions } = diffAgainstBaseline(live, baseline); + + assert.equal(regressions.length, 1); + assert.equal(regressions[0].baselineCount, 2); + assert.equal(regressions[0].liveCount, 3); +}); + +test("diffAgainstBaseline: reports (does not fail on) a count DECREASE as an improvement", () => { + const baseline = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 3 } }; + const live = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 1 } }; + + const { regressions, improvements } = diffAgainstBaseline(live, baseline); + + assert.equal(regressions.length, 0); + assert.equal(improvements.length, 1); + assert.equal(improvements[0].baselineCount, 3); + assert.equal(improvements[0].liveCount, 1); +}); + +test("diffAgainstBaseline: a baselined error that fully disappears is reported as an improvement, not a failure", () => { + const baseline = { "src/app/(dashboard)/dashboard/foo.tsx": { TS2339: 2 } }; + const live = {}; + + const { regressions, improvements } = diffAgainstBaseline(live, baseline); + + assert.equal(regressions.length, 0); + assert.equal(improvements.length, 1); + assert.equal(improvements[0].liveCount, 0); + assert.equal(improvements[0].baselineCount, 2); +}); diff --git a/tests/unit/check-pack-boot.test.ts b/tests/unit/check-pack-boot.test.ts new file mode 100644 index 0000000000..b22ca557b5 --- /dev/null +++ b/tests/unit/check-pack-boot.test.ts @@ -0,0 +1,57 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { pickTarball, evaluateBoot, pickPort } from "../../scripts/check/check-pack-boot.mjs"; + +// WS1.2 (T1, v3.8.49 quality plan) — pure-function guards for the tarball boot-smoke +// gate that kills the #7065 class (published artifact crashes on every boot because a +// packaging list drifted; 3rd recurrence). The end-to-end path runs in CI's +// package-artifact job; these tests pin the decision logic. + +const SCRIPT_PATH = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../scripts/check/check-pack-boot.mjs" +); + +test("pickTarball extracts the filename from npm pack --json output", () => { + assert.equal(pickTarball('[{"filename":"omniroute-3.8.49.tgz","size":1}]'), "omniroute-3.8.49.tgz"); +}); + +test("pickTarball normalizes scoped slashes to the on-disk dash form", () => { + assert.equal(pickTarball('[{"filename":"@scope/pkg-1.0.0.tgz"}]'), "@scope-pkg-1.0.0.tgz"); +}); + +test("pickTarball throws on empty/odd npm output instead of booting garbage", () => { + assert.throws(() => pickTarball("[]")); + assert.throws(() => pickTarball("{}")); +}); + +test("evaluateBoot passes on HTTP 200 + matching version, whatever the health status", () => { + const r = evaluateBoot(200, { version: "3.8.49", status: "warning" }, "3.8.49"); + assert.equal(r.ok, true); + assert.deepEqual(r.failures, []); +}); + +test("evaluateBoot fails on non-200, non-JSON body, and version mismatch", () => { + assert.equal(evaluateBoot(503, { version: "3.8.49" }, "3.8.49").ok, false); + assert.equal(evaluateBoot(200, null, "3.8.49").ok, false); + const wrong = evaluateBoot(200, { version: "3.8.48" }, "3.8.49"); + assert.equal(wrong.ok, false); + assert.match(wrong.failures[0], /3\.8\.48/); +}); + +test("pickPort stays inside the reserved smoke range for any pid", () => { + for (const seed of [0, 1, 4000, 65535, 123456]) { + const p = pickPort(seed); + assert.ok(p >= 23000 && p < 27000, `port ${p} out of range for seed ${seed}`); + } +}); + +test("source guard: the gate polls the real health endpoint of the INSTALLED binary", () => { + const src = readFileSync(SCRIPT_PATH, "utf8"); + assert.ok(src.includes('"install", "-g", "--prefix"'), "must install the packed tarball into a clean prefix"); + assert.ok(src.includes("/api/monitoring/health"), "must poll the health endpoint"); + assert.ok(src.indexOf("npm") < src.indexOf("spawn"), "pack+install must precede the boot spawn"); +}); diff --git a/tests/unit/check-test-masking-selfref-6634.test.ts b/tests/unit/check-test-masking-selfref-6634.test.ts index 6d91a2e7c2..b233a008f2 100644 --- a/tests/unit/check-test-masking-selfref-6634.test.ts +++ b/tests/unit/check-test-masking-selfref-6634.test.ts @@ -14,12 +14,14 @@ * (`if (file.endsWith("check-test-masking.test.ts")) continue;` in * scripts/check/check-test-masking.mjs) for precisely this reason — this test * asserts evaluateMasking() now applies the same exclusion for its diff-based - * tautology counters, using the real base(origin/main)/head(HEAD) diff of + * tautology counters, against the REAL current source of * tests/unit/check-test-masking.test.ts. */ import test from "node:test"; import assert from "node:assert/strict"; -import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import { countTautologies, @@ -28,16 +30,17 @@ import { } from "../../scripts/check/check-test-masking.mjs"; const FILE = "tests/unit/check-test-masking.test.ts"; - -function git(args: string[]): string { - return execFileSync("git", args, { encoding: "utf8" }); -} +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); test("#6634: check-test-masking.test.ts's own tautology fixtures must not self-flag as weakening", () => { - // origin/main predates the #6404 fixtures (countBareTautologies/scanBareTautologies - // tests) that legitimately embed tautology-pattern literals as string fixtures. - const baseSrc = git(["show", "origin/main:" + FILE]); - const headSrc = git(["show", "HEAD:" + FILE]); + // Read the REAL current source from disk rather than a git ref: the Unit Tests + // job checks out a shallow/single-ref tree with no origin/main, so `git show + // origin/main:` failed the shard before it ever exercised the masking + // behavior under test. An empty base models the file's pre-#6404 state (no + // fixtures), which maximizes headTaut - baseTaut — the strictest input for the + // exclusion this test asserts. + const baseSrc = ""; + const headSrc = fs.readFileSync(path.join(REPO_ROOT, FILE), "utf8"); const perFile = [ { diff --git a/tests/unit/classify-pr-changes.test.ts b/tests/unit/classify-pr-changes.test.ts index a2815786cc..4f4e30154d 100644 --- a/tests/unit/classify-pr-changes.test.ts +++ b/tests/unit/classify-pr-changes.test.ts @@ -15,7 +15,7 @@ import { classifyPaths } from "../../scripts/quality/classify-pr-changes.mjs"; test("pure docs PR → docs only (no code unit/lint bag)", () => { const c = classifyPaths(["docs/architecture/QUALITY_GATES.md", "README.md"]); - assert.deepEqual(c, { code: false, docs: true, i18n: false, workflow: false }); + assert.deepEqual(c, { code: false, docs: true, i18n: false, workflow: false, testsOnly: false }); }); test("openapi under docs/ → docs (contract gates live in docs-sync, not unit)", () => { @@ -26,7 +26,7 @@ test("openapi under docs/ → docs (contract gates live in docs-sync, not unit)" test("pure message catalog → i18n only (not full unit suite)", () => { const c = classifyPaths(["src/i18n/messages/en.json", "src/i18n/messages/ko.json"]); - assert.deepEqual(c, { code: false, docs: false, i18n: true, workflow: false }); + assert.deepEqual(c, { code: false, docs: false, i18n: true, workflow: false, testsOnly: false }); }); test("i18n tooling/scripts → i18n + code (tooling can break runtime paths)", () => { @@ -49,7 +49,7 @@ test("workflow change → workflow + code (gates protect the gates)", () => { test("production source → code", () => { const c = classifyPaths(["open-sse/handlers/chatCore.ts", "src/lib/db/core.ts"]); - assert.deepEqual(c, { code: true, docs: false, i18n: false, workflow: false }); + assert.deepEqual(c, { code: true, docs: false, i18n: false, workflow: false, testsOnly: false }); }); test("mixed docs + code → both flags (jobs union their filters)", () => { @@ -65,5 +65,31 @@ test("unknown path → code fail-safe (never skip heavy gates by accident)", () test("empty change list → all false (nothing to validate)", () => { const c = classifyPaths([]); - assert.deepEqual(c, { code: false, docs: false, i18n: false, workflow: false }); + assert.deepEqual(c, { code: false, docs: false, i18n: false, workflow: false, testsOnly: false }); +}); + +// WS3.1 (v3.8.49 quality plan) — testsOnly powers the hotfix/test-only fast lane: +// a diff touching ONLY tests/ (and no tests/e2e/ spec) does not change the served +// app, so the 9-shard E2E matrix adds wall-time without coverage. e2e specs are +// excluded from the shortcut — changing an e2e spec REQUIRES running e2e. + +test("testsOnly: pure unit-test diff → true (still code)", () => { + const c = classifyPaths(["tests/unit/foo.test.ts", "tests/integration/bar.test.ts"]); + assert.equal(c.testsOnly, true); + assert.equal(c.code, true); +}); + +test("testsOnly: any non-test file flips it false", () => { + const c = classifyPaths(["tests/unit/foo.test.ts", "src/lib/db/core.ts"]); + assert.equal(c.testsOnly, false); +}); + +test("testsOnly: touching an e2e spec is NOT tests-only (e2e must run)", () => { + const c = classifyPaths(["tests/e2e/login.spec.ts"]); + assert.equal(c.testsOnly, false); +}); + +test("testsOnly: empty change list → false (fail-safe)", () => { + const c = classifyPaths([]); + assert.equal(c.testsOnly, false); }); diff --git a/tests/unit/combo-diagnostics-trace.test.ts b/tests/unit/combo-diagnostics-trace.test.ts index be25e64c77..fe8bb546a4 100644 --- a/tests/unit/combo-diagnostics-trace.test.ts +++ b/tests/unit/combo-diagnostics-trace.test.ts @@ -79,3 +79,41 @@ test("combo diagnostics: secret containment — non-whitelisted fields never sur assert.ok(!serialized.includes("accessToken"), "no accessToken KEY survives"); assert.ok(!serialized.includes("token"), "no token KEY survives"); }); + +test("combo diagnostics: terminalReason with a non-Latin1 char (em dash) must not crash Response construction (#6612)", () => { + const terminalReason = "reasoning consumed 5/5 tokens — no content output"; + assert.doesNotThrow(() => { + const res = errorResponseWithComboDiagnostics( + 502, + `Upstream response failed quality validation: ${terminalReason}`, + { + poolSize: 4, + attempted: 1, + excluded: [{ provider: "deepseek", model: "deepseek-v4-flash-free", reason: "quality — bad" }], + attemptOrder: [{ provider: "deepseek", model: "deepseek-v4-flash-free" }], + terminalReason, + } + ); + assert.equal(res.status, 502); + }); +}); + +test("combo diagnostics: JSON body keeps the original non-Latin1 text even though headers are ASCII-sanitized (#6612)", async () => { + const terminalReason = "reasoning consumed 5/5 tokens — no content output"; + const res = errorResponseWithComboDiagnostics( + 502, + `Upstream response failed quality validation: ${terminalReason}`, + { + poolSize: 1, + attempted: 1, + excluded: [], + attemptOrder: [{ provider: "deepseek", model: "deepseek-v4-flash-free" }], + terminalReason, + } + ); + // Header value must be a valid Latin1 ByteString — em dash (U+2014) replaced. + assert.equal(res.headers.get("x-omniroute-combo-terminal-reason"), terminalReason.replace("—", "?")); + const body = await res.json(); + // JSON body keeps the original, readable (unsanitized) em dash. + assert.equal(body.diagnostics.terminalReason, terminalReason); +}); diff --git a/tests/unit/combo-proxy-assignments-parse-7149.test.ts b/tests/unit/combo-proxy-assignments-parse-7149.test.ts new file mode 100644 index 0000000000..7afb82b487 --- /dev/null +++ b/tests/unit/combo-proxy-assignments-parse-7149.test.ts @@ -0,0 +1,32 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { parseComboProxyAssignmentIds } from "../../src/app/(dashboard)/dashboard/combos/useComboProxyAssignments.ts"; + +test("#7149: parseComboProxyAssignmentIds extracts scopeIds from valid combo assignments", () => { + const data = { + items: [ + { scopeId: "combo-1", proxyId: "proxy-1", scope: "combo" }, + { scopeId: "combo-2", proxyId: "proxy-2", scope: "combo" }, + ], + }; + assert.deepEqual(parseComboProxyAssignmentIds(data), ["combo-1", "combo-2"]); +}); + +test("#7149: parseComboProxyAssignmentIds drops entries missing scopeId or proxyId", () => { + const data = { + items: [ + { scopeId: "combo-1", proxyId: "proxy-1" }, + { scopeId: "combo-2", proxyId: null }, + { scopeId: null, proxyId: "proxy-3" }, + {}, + ], + }; + assert.deepEqual(parseComboProxyAssignmentIds(data), ["combo-1"]); +}); + +test("#7149: parseComboProxyAssignmentIds returns [] for missing/malformed items", () => { + assert.deepEqual(parseComboProxyAssignmentIds(null), []); + assert.deepEqual(parseComboProxyAssignmentIds(undefined), []); + assert.deepEqual(parseComboProxyAssignmentIds({}), []); + assert.deepEqual(parseComboProxyAssignmentIds({ items: "not-an-array" }), []); +}); diff --git a/tests/unit/combo-scope-proxy-dead-7149.test.ts b/tests/unit/combo-scope-proxy-dead-7149.test.ts new file mode 100644 index 0000000000..8d301763e9 --- /dev/null +++ b/tests/unit/combo-scope-proxy-dead-7149.test.ts @@ -0,0 +1,87 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-proxy-7149-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const combosDb = await import("../../src/lib/db/combos.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); + +type ProxyResolutionLike = { + proxy?: { host?: string } | null; + level?: string; + levelId?: string | null; +} | null; + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("#7149: a proxy assigned to a Combo via the dashboard (registry scope='combo') is honored when resolving the proxy for a request routed through that combo", async () => { + await resetStorage(); + + const comboProxy = await proxiesDb.createProxy({ + name: "Combo-Assigned Proxy", + type: "http", + host: "10.20.30.40", + port: 8888, + }); + assert.ok(comboProxy?.id); + + const combo = await combosDb.createCombo({ + name: "diy_deepseek-v4-flash", + strategy: "round-robin", + models: ["openai/gpt-4"], + }); + const comboRecord = combo as Record; + assert.ok(comboRecord?.id); + const comboId = comboRecord.id as string; + + const assignment = await proxiesDb.assignProxyToScope("combo", comboId, comboProxy!.id); + assert.ok(assignment, "assignProxyToScope('combo', ...) should persist the assignment"); + + const directRegistryLookup = (await proxiesDb.resolveProxyForScopeFromRegistry( + "combo", + comboId + )) as ProxyResolutionLike; + assert.ok( + directRegistryLookup?.proxy, + "the registry must be able to answer a direct combo-scope lookup" + ); + assert.equal(directRegistryLookup?.proxy?.host, "10.20.30.40"); + + const connection = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + apiKey: "sk-test-1234", + name: "openai-account-1", + }); + const connectionRecord = connection as Record | null; + const connectionId = connectionRecord?.id as string; + assert.ok(connectionId, "test setup requires a real connection id"); + + const resolved = (await settingsDb.resolveProxyForConnection( + connectionId + )) as ProxyResolutionLike; + + assert.equal( + resolved?.level, + "combo", + `expected the combo-assigned proxy to be resolved (level="combo"), got level="${resolved?.level}" — the registry-based combo proxy assignment is never consulted by resolveProxyForConnection()` + ); + assert.equal(resolved?.proxy?.host, "10.20.30.40"); +}); diff --git a/tests/unit/compression/adaptive-context-budget-config.test.ts b/tests/unit/compression/adaptive-context-budget-config.test.ts new file mode 100644 index 0000000000..4dd4acb68c --- /dev/null +++ b/tests/unit/compression/adaptive-context-budget-config.test.ts @@ -0,0 +1,84 @@ +// Regression test for #7005 — adaptive context-budget dial not configurable. +// +// The compute engine for the adaptive context-budget ("dial") shipped in PR #4716 +// (Phase 4C), but it was never wired to persistence or the API: the PUT schema +// rejected any `contextBudget` payload (strict schema, no such key) and the DB-backed +// GET path never surfaced a `contextBudget` field. This test proves both halves of +// the wiring: the Zod schema accepts a `contextBudget` write, and the DB read/write +// path round-trips it. +import { describe, it, beforeEach, afterEach, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync( + path.join(os.tmpdir(), "omniroute-adaptive-context-budget-db-") +); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../../src/lib/db/core.ts"); +const { getCompressionSettings, updateCompressionSettings } = await import( + "../../../src/lib/db/compression.ts" +); +const { compressionSettingsUpdateSchema } = await import( + "../../../src/shared/validation/compressionConfigSchemas.ts" +); +const { DEFAULT_CONTEXT_BUDGET } = await import( + "../../../open-sse/services/compression/adaptiveCompression/types.ts" +); + +beforeEach(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +afterEach(() => { + core.resetDbInstance(); +}); + +after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +describe("bug #7005: adaptive context-budget dial is configurable", () => { + it("compressionSettingsUpdateSchema accepts a contextBudget write", () => { + const result = compressionSettingsUpdateSchema.safeParse({ + contextBudget: { + mode: "floor", + policy: "percentage", + outputReserve: 2048, + safetyMargin: 512, + pct: 0.75, + absoluteBudget: 0, + }, + }); + assert.equal(result.success, true, JSON.stringify("error" in result ? result.error : null)); + }); + + it("getCompressionSettings() defaults contextBudget to DEFAULT_CONTEXT_BUDGET when absent", async () => { + const settings = await getCompressionSettings(); + assert.deepEqual(settings.contextBudget, DEFAULT_CONTEXT_BUDGET); + }); + + it("updateCompressionSettings() persists a partial contextBudget merge", async () => { + await updateCompressionSettings({ + contextBudget: { ...DEFAULT_CONTEXT_BUDGET, mode: "floor", policy: "absolute", absoluteBudget: 8000 }, + }); + const settings = await getCompressionSettings(); + assert.equal(settings.contextBudget?.mode, "floor"); + assert.equal(settings.contextBudget?.policy, "absolute"); + assert.equal(settings.contextBudget?.absoluteBudget, 8000); + // Untouched fields keep their defaults (this is a JSON-column replace like ultra/aggressive, + // not a deep merge — the caller sends the full object, mirroring the existing pattern). + assert.equal(settings.contextBudget?.outputReserve, DEFAULT_CONTEXT_BUDGET.outputReserve); + }); +}); diff --git a/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts b/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts new file mode 100644 index 0000000000..8b00ac5dc3 --- /dev/null +++ b/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts @@ -0,0 +1,99 @@ +import { describe, it, before, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-6996-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const { DuckDuckGoWebExecutor, STATUS_URL } = await import( + "../../open-sse/executors/duckduckgo-web.ts" +); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const executeInputBase = { + model: "gpt-4o-mini", + body: { + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hi" }], + stream: false, + }, + stream: false, + credentials: {}, +}; + +describe("#6996 DuckDuckGo VQD 429 misclassification", () => { + let originalFetch: typeof fetch; + + before(() => { + originalFetch = globalThis.fetch; + }); + + after(() => { + globalThis.fetch = originalFetch; + resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + }); + + it("propagates upstream 429 instead of masking it as a generic 503", async () => { + // Set the mock AFTER the module import so it wins over + // open-sse/utils/proxyFetch.ts's own module-load-time + // `globalThis.fetch = patchedFetch` side effect. + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : (input as URL | Request).toString(); + if (url === STATUS_URL) { + return new Response("", { + status: 429, + headers: { "Retry-After": "30" }, + }); + } + if (url.includes("/duckchat/v1/chat")) { + throw new Error("unexpected chat POST reached without a VQD token"); + } + return new Response("", { status: 200 }); + }) as typeof fetch; + + const executor = new DuckDuckGoWebExecutor(); + const response = await executor.execute(executeInputBase); + + const httpResponse = + response instanceof Response + ? response + : (response as { response: Response }).response; + const bodyText = await httpResponse.text(); + + assert.equal( + httpResponse.status, + 429, + `expected the executor to surface DuckDuckGo's real 429 rate-limit status, got ${httpResponse.status} (body: ${bodyText})` + ); + }); + + it("still returns 503 fallback for a genuine 5xx status on the VQD endpoint", async () => { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : (input as URL | Request).toString(); + if (url === STATUS_URL) { + return new Response("", { status: 500 }); + } + if (url.includes("/duckchat/v1/chat")) { + throw new Error("unexpected chat POST reached without a VQD token"); + } + return new Response("", { status: 200 }); + }) as typeof fetch; + + const executor = new DuckDuckGoWebExecutor(); + const response = await executor.execute(executeInputBase); + + const httpResponse = + response instanceof Response + ? response + : (response as { response: Response }).response; + const bodyText = await httpResponse.text(); + + assert.equal( + httpResponse.status, + 503, + `expected the executor to keep the 503 fallback for a genuine upstream 5xx, got ${httpResponse.status} (body: ${bodyText})` + ); + }); +}); diff --git a/tests/unit/free-pool-tab.test.tsx b/tests/unit/free-pool-tab.test.tsx index 6279174fa4..91ee249776 100644 --- a/tests/unit/free-pool-tab.test.tsx +++ b/tests/unit/free-pool-tab.test.tsx @@ -91,13 +91,13 @@ afterEach(() => { // ── Tests ───────────────────────────────────────────────────────────────────── describe("FreePoolTab source toggles", () => { - it("renders a toggle group with exactly 3 buttons", async () => { + it("renders a toggle group with exactly 4 buttons", async () => { const el = renderTab(); await waitForCondition(() => el.querySelector("[role='group']") !== null); const bar = el.querySelector("[role='group']")!; expect(bar).toBeTruthy(); const buttons = bar.querySelectorAll("button"); - expect(buttons.length).toBe(3); + expect(buttons.length).toBe(4); }); it("all toggles start enabled (aria-pressed=true)", async () => { @@ -160,7 +160,7 @@ describe("FreePoolTab source toggles", () => { expect(stored).toContain("1proxy"); }); - it("button labels are 1proxy, Proxifly, IPLocate", async () => { + it("button labels are 1proxy, Proxifly, IPLocate, Webshare", async () => { const el = renderTab(); await waitForCondition(() => el.querySelector("[role='group']") !== null); const texts = Array.from(el.querySelector("[role='group']")!.querySelectorAll("button")).map( @@ -169,6 +169,7 @@ describe("FreePoolTab source toggles", () => { expect(texts).toContain("1proxy"); expect(texts).toContain("Proxifly"); expect(texts).toContain("IPLocate"); + expect(texts).toContain("Webshare"); }); }); diff --git a/tests/unit/gemini-web.test.ts b/tests/unit/gemini-web.test.ts index 5e4a25d0cf..8ae1fe9675 100644 --- a/tests/unit/gemini-web.test.ts +++ b/tests/unit/gemini-web.test.ts @@ -266,15 +266,20 @@ test("#2832: GeminiWebExecutor catch block sanitizes Playwright launch errors (i // ─── StreamGenerate parsing ───────────────────────────────────────────────── -test("parseStreamResponse concatenates Gemini Web text from multiple wrb.fr chunks", () => { +test("parseStreamResponse keeps only the final cumulative StreamGenerate snapshot (no duplication) — regression for #7163", () => { const makeChunk = (text: string) => { const inner = new Array(80).fill(null); inner[4] = [[null, [text]]]; return `[["wrb.fr", null, ${JSON.stringify(JSON.stringify(inner))}]]`; }; - const raw = `)]}'\n10\n${makeChunk("First ")}\n5\n${makeChunk("chunk")}`; - assert.equal(parseStreamResponse(raw), "First chunk"); + // Gemini's StreamGenerate frames are CUMULATIVE snapshots: each later frame + // repeats the full answer generated so far, not just the new characters. + const frame1 = "Hello!"; + const frame2 = "Hello! How can I"; + const frame3 = "Hello! How can I help you out today?"; + const raw = `)]}'\n10\n${makeChunk(frame1)}\n5\n${makeChunk(frame2)}\n5\n${makeChunk(frame3)}`; + assert.equal(parseStreamResponse(raw), frame3); }); test("parseStreamResponse ignores wrb.fr lines whose first entry is not an array", () => { diff --git a/tests/unit/homolog-admin-client.test.ts b/tests/unit/homolog-admin-client.test.ts new file mode 100644 index 0000000000..225f66073c --- /dev/null +++ b/tests/unit/homolog-admin-client.test.ts @@ -0,0 +1,17 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { extractJwtCookie, extractApiKey } from "../../scripts/homolog/lib/adminClient.mjs"; + +test("extrai o cookie JWT do set-cookie do login", () => { + const jwt = extractJwtCookie(["auth_token=abc.def.ghi; Path=/; HttpOnly; SameSite=Lax"]); + assert.equal(jwt, "auth_token=abc.def.ghi"); +}); + +test("retorna null sem set-cookie de token", () => { + assert.equal(extractJwtCookie(["other=1; Path=/"]), null); +}); + +test("extrai key e id do POST /api/keys", () => { + const r = extractApiKey({ key: "or-abc123", id: "k1", name: "homolog-run" }); + assert.deepEqual(r, { key: "or-abc123", id: "k1" }); +}); diff --git a/tests/unit/homolog-parity.test.ts b/tests/unit/homolog-parity.test.ts new file mode 100644 index 0000000000..0c3d779f41 --- /dev/null +++ b/tests/unit/homolog-parity.test.ts @@ -0,0 +1,29 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { evaluateParity } from "../../scripts/homolog/lib/parity.mjs"; + +test("parity OK quando health bate com a versão esperada", () => { + const r = evaluateParity( + { status: "healthy", version: "3.8.49" }, + { expectedVersion: "3.8.49", httpStatus: 200 } + ); + assert.equal(r.ok, true); + assert.deepEqual(r.failures, []); +}); + +test("parity falha listando cada divergência", () => { + const r = evaluateParity( + { status: "degraded", version: "3.8.47" }, + { expectedVersion: "3.8.49", httpStatus: 200 } + ); + assert.equal(r.ok, false); + assert.equal(r.failures.length, 2); // status!=healthy, version mismatch +}); + +test("parity falha em HTTP não-200 mesmo com body bom", () => { + const r = evaluateParity( + { status: "healthy", version: "3.8.49" }, + { expectedVersion: "3.8.49", httpStatus: 503 } + ); + assert.equal(r.ok, false); +}); diff --git a/tests/unit/homolog-promptfoo-ctrf.test.ts b/tests/unit/homolog-promptfoo-ctrf.test.ts new file mode 100644 index 0000000000..d22dd652ee --- /dev/null +++ b/tests/unit/homolog-promptfoo-ctrf.test.ts @@ -0,0 +1,18 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { promptfooToCtrf } from "../../scripts/homolog/lib/promptfooToCtrf.mjs"; + +test("mapeia resultados do promptfoo para tests CTRF", () => { + const ctrf = promptfooToCtrf({ + results: { + results: [ + { provider: { label: "openai" }, success: true, latencyMs: 812 }, + { provider: { label: "grok" }, success: false, latencyMs: 30000, error: "timeout" }, + ], + }, + }); + assert.equal(ctrf.results.summary.tests, 2); + assert.equal(ctrf.results.summary.passed, 1); + assert.equal(ctrf.results.tests[1].status, "failed"); + assert.equal(ctrf.results.tests[1].name, "provider-smoke: grok"); +}); diff --git a/tests/unit/homolog-provider-tiers.test.ts b/tests/unit/homolog-provider-tiers.test.ts new file mode 100644 index 0000000000..3c793da73f --- /dev/null +++ b/tests/unit/homolog-provider-tiers.test.ts @@ -0,0 +1,24 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { pickSmokeModels } from "../../scripts/homolog/lib/providerTiers.mjs"; + +const CATALOG = [ + { id: "openai/gpt-5-mini" }, + { id: "openai/gpt-5" }, + { id: "anthropic/claude-sonnet-5" }, + { id: "mistral/mistral-small" }, + { id: "grok/grok-4-fast" }, +]; + +test("1 modelo por provider crítico (o primeiro do catálogo)", () => { + const picks = pickSmokeModels(CATALOG, ["openai", "anthropic", "grok"]); + assert.deepEqual( + picks.map((p) => p.model), + ["openai/gpt-5-mini", "anthropic/claude-sonnet-5", "grok/grok-4-fast"] + ); +}); + +test("provider crítico ausente do catálogo vira miss reportável", () => { + const picks = pickSmokeModels(CATALOG, ["openai", "nvidia"]); + assert.equal(picks.find((p) => p.provider === "nvidia").model, null); +}); diff --git a/tests/unit/homolog-sse-parser.test.ts b/tests/unit/homolog-sse-parser.test.ts new file mode 100644 index 0000000000..b51c226385 --- /dev/null +++ b/tests/unit/homolog-sse-parser.test.ts @@ -0,0 +1,28 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { parseSseChunk, summarizeStream } from "../../scripts/homolog/lib/sseCheck.mjs"; + +test("parseSseChunk separa eventos data: e detecta [DONE]", () => { + const events = parseSseChunk('data: {"choices":[{"delta":{"content":"O"}}]}\n\ndata: [DONE]\n\n'); + assert.equal(events.length, 2); + assert.equal(events[1], "[DONE]"); +}); + +test("parseSseChunk acha data: mesmo precedido de comment-lines SSE no mesmo bloco", () => { + // Formato real da VPS (v3.8.47): trailers de telemetria como comments (`: x-omniroute-*`) + // no MESMO bloco do data: [DONE] — o parser não pode olhar só o início do bloco. + const chunk = + 'data: {"choices":[{"delta":{"content":"OK"}}]}\n\n' + + ": x-omniroute-cache-hit=false\n: x-omniroute-latency-ms=67\ndata: [DONE]\n\n"; + const events = parseSseChunk(chunk); + assert.deepEqual(events, ['{"choices":[{"delta":{"content":"OK"}}]}', "[DONE]"]); +}); + +test("summarizeStream exige >=1 delta de conteúdo e terminador [DONE]", () => { + const good = summarizeStream(['{"choices":[{"delta":{"content":"OK"}}]}', "[DONE]"]); + assert.equal(good.ok, true); + const noDone = summarizeStream(['{"choices":[{"delta":{"content":"OK"}}]}']); + assert.equal(noDone.ok, false); + const noContent = summarizeStream(["[DONE]"]); + assert.equal(noContent.ok, false); +}); diff --git a/tests/unit/issue-7071-ollama-session-quota.test.ts b/tests/unit/issue-7071-ollama-session-quota.test.ts new file mode 100644 index 0000000000..25b297c0ad --- /dev/null +++ b/tests/unit/issue-7071-ollama-session-quota.test.ts @@ -0,0 +1,103 @@ +/** + * Issue #7071 — Ollama Cloud's 5-hour "session" usage-limit 429 is never + * recognized as quota-exhausted. The upstream returns a body like: + * "you () have reached your session usage limit" + * + * This exactly mirrors the already-fixed "weekly usage limit" gap (#3709, + * #6638): ollama-cloud is an apikey-category provider (not oauth), so the + * oauth-only `shouldUseQuotaSignal` gate in checkFallbackError skips the + * generic subscription-quota-text branch (#2321) for its 429s. Without a + * dedicated, ungated session check the account fell through to the generic + * 429 backoff (~3s, capped low) and got retried within the same 5-hour + * session window instead of cooling down for the session's duration — + * combo/LKGP routing cycled back to the "exhausted" account instead of + * advancing to the next one. + * + * This test proves: (1) the session-usage-limit text is classified as + * QUOTA_EXHAUSTED with a cooldown far longer than the generic backoff cap, + * for BOTH apikey and oauth provider categories, and (2) unrelated + * session-expired/auth wording and the sibling weekly-quota text are + * unaffected. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { checkFallbackError } = await import("../../open-sse/services/accountFallback.ts"); +const { isSessionUsageLimitText, buildSessionQuotaFallback, isWeeklyUsageLimitText } = + await import("../../open-sse/services/quotaTextCooldowns.ts"); +const { RateLimitReason, BACKOFF_CONFIG } = await import("../../open-sse/config/constants.ts"); +const { BACKOFF_CONFIG: ERROR_BACKOFF_CONFIG } = await import("../../open-sse/config/errorConfig.ts"); + +const SESSION_BODY = "you (acme-corp) have reached your session usage limit"; +const SESSION_COOLDOWN_MS = 5 * 60 * 60 * 1000; // 5 hours + +test("#7071 sanity: weekly text IS recognized (already fixed by #3709/#6638)", () => { + assert.equal(isWeeklyUsageLimitText("you (acme-corp) have reached your weekly usage limit"), true); +}); + +test("#7071 isSessionUsageLimitText matches the ollama-cloud 429 body", () => { + assert.equal(isSessionUsageLimitText(SESSION_BODY.toLowerCase()), true); + assert.equal(isSessionUsageLimitText("session limit reached, try later"), true); + assert.equal(isSessionUsageLimitText("rate_limit_exceeded: too many requests"), false); + // Must not false-positive on unrelated "session expired" auth errors. + assert.equal(isSessionUsageLimitText("your session has expired, please log in again"), false); + assert.equal(isSessionUsageLimitText("session token invalid"), false); +}); + +test("#7071 buildSessionQuotaFallback returns a 5h QUOTA_EXHAUSTED cooldown, far above the generic backoff cap", () => { + const result = buildSessionQuotaFallback(SESSION_BODY); + assert.ok(result, "expected a non-null fallback for session-usage-limit text"); + assert.equal(result!.reason, RateLimitReason.QUOTA_EXHAUSTED); + assert.equal(result!.cooldownMs, SESSION_COOLDOWN_MS); + assert.ok(result!.cooldownMs > (ERROR_BACKOFF_CONFIG.max ?? BACKOFF_CONFIG.max)); +}); + +test("#7071 buildSessionQuotaFallback returns null for unrelated error text", () => { + assert.equal(buildSessionQuotaFallback("rate_limit_exceeded: too many requests"), null); + assert.equal(buildSessionQuotaFallback("your session has expired, please log in again"), null); +}); + +test("#7071 BUG: checkFallbackError misclassifies ollama-cloud session-quota 429 as generic RATE_LIMIT_EXCEEDED instead of QUOTA_EXHAUSTED", () => { + const out = checkFallbackError( + 429, + SESSION_BODY, + 0, // backoffLevel + null, // model + "ollama-cloud", // provider (apikey category) + null, // headers + null, // profileOverride + null // structuredError + ); + + assert.equal(out.shouldFallback, true); + assert.equal( + out.reason, + RateLimitReason.QUOTA_EXHAUSTED, + `expected QUOTA_EXHAUSTED for session-usage-limit text, got reason=${out.reason} cooldownMs=${out.cooldownMs}` + ); + assert.equal(out.cooldownMs, SESSION_COOLDOWN_MS); +}); + +test("#7071 checkFallbackError: oauth-category provider with session-limit text also gets the long cooldown", () => { + const out = checkFallbackError(429, SESSION_BODY, 0, null, "claude", null, null, null); + assert.equal(out.reason, RateLimitReason.QUOTA_EXHAUSTED); + assert.equal(out.cooldownMs, SESSION_COOLDOWN_MS); +}); + +test("#7071 checkFallbackError: ollama-cloud generic rate-limit body is unaffected (no false positive)", () => { + const out = checkFallbackError( + 429, + "rate_limit_exceeded: too many requests", + 0, + null, + "ollama-cloud", + null, + null, + null + ); + assert.equal(out.reason, RateLimitReason.RATE_LIMIT_EXCEEDED); + assert.ok( + out.cooldownMs <= 2 * 60 * 1000, + "generic rate limit text must keep the normal short backoff, not the 5h session cooldown" + ); +}); diff --git a/tests/unit/listCapabilities-a2a.test.ts b/tests/unit/listCapabilities-a2a.test.ts index a51357d712..60e636b163 100644 --- a/tests/unit/listCapabilities-a2a.test.ts +++ b/tests/unit/listCapabilities-a2a.test.ts @@ -3,7 +3,7 @@ * * Verifies: * - Return shape matches §3.7 contract - * - Markdown table contains all 43 skill IDs + * - Markdown table contains all 44 skill IDs * - Coverage bounds are within declared totals * - metadata.source === "agent-skills-catalog" * - metadata.generatedAt is an ISO datetime string @@ -31,7 +31,7 @@ test("executeListCapabilities returns shape matching §3.7 contract", async () = const { metadata } = result; assert.ok(metadata, "metadata exists"); assert.equal(metadata.source, "agent-skills-catalog", "metadata.source matches"); - assert.equal(metadata.totalSkills, 44, "metadata.totalSkills === 44 (43 + config)"); + assert.equal(metadata.totalSkills, 45, "metadata.totalSkills === 45 (44 + config)"); assert.ok(metadata.coverage, "metadata.coverage exists"); assert.ok(metadata.coverage.api, "metadata.coverage.api exists"); assert.ok(metadata.coverage.cli, "metadata.coverage.cli exists"); @@ -39,12 +39,12 @@ test("executeListCapabilities returns shape matching §3.7 contract", async () = assert.equal(metadata.coverage.cli.total, 20, "cli.total === 20"); }); -test("executeListCapabilities markdown table contains all 43 API+CLI skill IDs", async () => { +test("executeListCapabilities markdown table contains all 44 API+CLI skill IDs", async () => { const result = await executeListCapabilities(stubTask); const content = result.artifacts[0].content; const allIds = [...API_SKILL_IDS, ...CLI_SKILL_IDS] as string[]; - assert.equal(allIds.length, 43, "API+CLI catalog declares 43 skill IDs"); + assert.equal(allIds.length, 44, "API+CLI catalog declares 44 skill IDs"); for (const id of allIds) { assert.ok(content.includes(id), `Markdown table missing skill ID: ${id}`); @@ -58,11 +58,11 @@ test("metadata.coverage.api.have is within [0, 23]", async () => { assert.ok(api.have <= 23, "api.have <= 23"); }); -test("metadata.coverage.cli.have is within [0, 20]", async () => { +test("metadata.coverage.cli.have is within [0, 21]", async () => { const result = await executeListCapabilities(stubTask); const { cli } = result.metadata.coverage; assert.ok(cli.have >= 0, "cli.have >= 0"); - assert.ok(cli.have <= 20, "cli.have <= 20"); + assert.ok(cli.have <= 21, "cli.have <= 21"); }); test("metadata.generatedAt is a valid ISO datetime", async () => { diff --git a/tests/unit/main-server-keepalive-timeout-7003.test.ts b/tests/unit/main-server-keepalive-timeout-7003.test.ts new file mode 100644 index 0000000000..fd20f19774 --- /dev/null +++ b/tests/unit/main-server-keepalive-timeout-7003.test.ts @@ -0,0 +1,202 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import net from "node:net"; +import { getMainServerTimeoutConfig } from "../../src/shared/utils/runtimeTimeouts.ts"; + +// #7003 — JetBrains AI Assistant ("Test Connection" / completions) reported +// "HTTP/1.1 header parser received no bytes". The main OmniRoute server +// (scripts/dev/run-next.mjs) boots a bare `http.createServer(...)` and never +// configures `keepAliveTimeout`/`headersTimeout`, leaving Node's http.Server +// default of keepAliveTimeout=5_000ms with no `Keep-Alive: timeout=N` response +// hint. JetBrains AI Assistant's JVM `java.net.http.HttpClient` connection pool +// can reuse a socket idle for longer than that window; the server has already +// torn the socket down, so the client gets 0 response bytes back instead of a +// fresh HTTP response. +// +// This spec proves both halves: +// 1. `getMainServerTimeoutConfig()` raises the defaults well above Node's +// unconfigured 5_000ms window (the actual fix wired into run-next.mjs). +// 2. A bare http.Server left at Node's defaults drops a socket reused after +// an idle gap past 5s, while the same server configured via +// `getMainServerTimeoutConfig()` keeps serving the reused connection. + +describe("#7003 getMainServerTimeoutConfig", () => { + it("defaults keepAliveTimeout/headersTimeout well above Node's 5_000ms default", () => { + const config = getMainServerTimeoutConfig({}); + assert.equal(config.keepAliveTimeoutMs, 65_000); + assert.equal(config.headersTimeoutMs, 66_000); + assert.ok(config.keepAliveTimeoutMs > 5_000, "must exceed Node's unconfigured default"); + assert.ok( + config.headersTimeoutMs > config.keepAliveTimeoutMs, + "headersTimeout must stay above keepAliveTimeout per Node's own requirement" + ); + }); + + it("honors env overrides and keeps headersTimeout coherent with a raised keepAliveTimeout", () => { + const config = getMainServerTimeoutConfig({ + MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "120000", + MAIN_SERVER_HEADERS_TIMEOUT_MS: "121000", + }); + assert.equal(config.keepAliveTimeoutMs, 120_000); + assert.equal(config.headersTimeoutMs, 121_000); + }); + + it("bumps an inconsistent explicit headersTimeout override above keepAliveTimeout", () => { + const config = getMainServerTimeoutConfig({ + MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "120000", + MAIN_SERVER_HEADERS_TIMEOUT_MS: "1000", + }); + assert.equal(config.keepAliveTimeoutMs, 120_000); + assert.equal(config.headersTimeoutMs, 121_000); + }); + + it("falls back to defaults on invalid env values", () => { + const config = getMainServerTimeoutConfig({ + MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "not-a-number", + }); + assert.equal(config.keepAliveTimeoutMs, 65_000); + }); +}); + +/** + * Sends a raw HTTP/1.1 GET over an already-connected keep-alive socket and + * resolves with whatever bytes arrive within a short settle window (empty + * string if nothing comes back — the exact "0 bytes back" failure mode + * JetBrains AI Assistant surfaces as "header parser received no bytes"). + * + * The socket is opened with `allowHalfOpen: true` so it faithfully mimics a + * JVM/OkHttp-style client: Node's default `allowHalfOpen: false` proactively + * ends the writable side the instant it processes an incoming FIN, turning + * the reused write into a synchronous "socket has been ended" error instead + * of the real-world race — a write that is accepted locally (the server + * already destroyed the connection, so it never arrives) whose response + * settles as 0 bytes. + */ +function sendKeepAliveRequest(socket: net.Socket, port: number): Promise { + return new Promise((resolve) => { + let received = ""; + let settleTimer: NodeJS.Timeout; + const finish = () => { + socket.off("data", onData); + clearTimeout(settleTimer); + resolve(received); + }; + // A short settle window once the full chunked response has arrived (fast + // path); a generous cap in case nothing ever comes back — the torn-down + // connection case this test proves, and a safety margin against first-run + // JIT/module-load jitter under the test runner. + const onData = (chunk: Buffer) => { + received += chunk.toString("utf8"); + if (received.endsWith("0\r\n\r\n")) { + clearTimeout(settleTimer); + settleTimer = setTimeout(finish, 50); + } + }; + socket.on("data", onData); + socket.write(`GET / HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nConnection: keep-alive\r\n\r\n`); + settleTimer = setTimeout(finish, 3_000); + }); +} + +function startEchoServer(configure: (server: http.Server) => void): Promise { + return new Promise((resolve) => { + const server = http.createServer((_req, res) => { + res.writeHead(200, { "content-type": "text/plain" }); + res.end("ok"); + }); + configure(server); + server.listen(0, "127.0.0.1", () => resolve(server)); + }); +} + +async function withServer( + configure: (server: http.Server) => void, + run: (port: number) => Promise +): Promise { + const server = await startEchoServer(configure); + try { + const address = server.address(); + if (typeof address !== "object" || address === null) { + throw new Error("expected server to bind a TCP address"); + } + await run(address.port); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } +} + +// Node's default keepAliveTimeout is 5_000ms, but the server only starts that +// timer once the response has fully flushed and there is a small amount of +// internal scheduling overhead before the socket is actually torn down — +// empirically ~5.8-6s end-to-end on loopback. 6.5s reliably clears that +// window without relying on a hair-trigger race. +const IDLE_GAP_MS = 6_500; + +describe("#7003 keep-alive socket reuse across an idle gap", () => { + it( + "current Node defaults (keepAliveTimeout=5000ms): a pooled socket reused after 6.5s idle gets 0 bytes back", + { timeout: 30_000 }, + async () => { + await withServer( + () => { + /* leave Node's http.Server defaults untouched (keepAliveTimeout=5000ms) */ + }, + async (port) => { + const socket = net.connect({ port, host: "127.0.0.1", allowHalfOpen: true }); + await new Promise((resolve, reject) => { + socket.once("connect", () => resolve()); + socket.once("error", reject); + }); + + const first = await sendKeepAliveRequest(socket, port); + assert.match(first, /200/, "first request on a fresh socket must succeed"); + + await new Promise((resolve) => setTimeout(resolve, IDLE_GAP_MS)); + + const second = await sendKeepAliveRequest(socket, port); + assert.equal( + second, + "", + "reusing the idle-torn-down socket must get exactly 0 bytes back (the reported bug)" + ); + socket.destroy(); + } + ); + } + ); + + it( + "fixed config (getMainServerTimeoutConfig): the same reused connection stays alive past 6.5s idle", + { timeout: 30_000 }, + async () => { + const fixedTimeouts = getMainServerTimeoutConfig({}); + await withServer( + (server) => { + server.keepAliveTimeout = fixedTimeouts.keepAliveTimeoutMs; + server.headersTimeout = fixedTimeouts.headersTimeoutMs; + }, + async (port) => { + const socket = net.connect({ port, host: "127.0.0.1", allowHalfOpen: true }); + await new Promise((resolve, reject) => { + socket.once("connect", () => resolve()); + socket.once("error", reject); + }); + + const first = await sendKeepAliveRequest(socket, port); + assert.match(first, /200/, "first request on a fresh socket must succeed"); + + await new Promise((resolve) => setTimeout(resolve, IDLE_GAP_MS)); + + const second = await sendKeepAliveRequest(socket, port); + assert.match( + second, + /200/, + "the reused connection must still get a valid response after the fix" + ); + socket.destroy(); + } + ); + } + ); +}); diff --git a/tests/unit/main-server-timeouts-parity.test.ts b/tests/unit/main-server-timeouts-parity.test.ts new file mode 100644 index 0000000000..7a8374c942 --- /dev/null +++ b/tests/unit/main-server-timeouts-parity.test.ts @@ -0,0 +1,39 @@ +import test from "node:test"; +import assert from "node:assert"; +import { getMainServerTimeoutConfig as mjsImpl } from "../../scripts/dev/main-server-timeouts.mjs"; +import { getMainServerTimeoutConfig as tsImpl } from "../../src/shared/utils/runtimeTimeouts.ts"; + +// The shipped server-ws.mjs uses the SIBLING scripts/dev/main-server-timeouts.mjs +// (a ../../src import escapes the package after the dist copy — 2026-07-15 boot +// crash, #7065 class). This parity matrix is the anti-drift guard between the +// sibling and the canonical src/shared/utils/runtimeTimeouts.ts implementation. +const ENV_MATRIX: Record[] = [ + {}, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "70000" }, + { MAIN_SERVER_HEADERS_TIMEOUT_MS: "80000" }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "90000", MAIN_SERVER_HEADERS_TIMEOUT_MS: "10000" }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "0", MAIN_SERVER_HEADERS_TIMEOUT_MS: "0" }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "abc" }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: " " }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "-5" }, + { MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "1234.9" }, +]; + +test("sibling main-server-timeouts.mjs stays in parity with runtimeTimeouts.ts", () => { + for (const env of ENV_MATRIX) { + assert.deepStrictEqual( + mjsImpl(env), + tsImpl(env), + `divergence for env ${JSON.stringify(env)}` + ); + } +}); + +test("invalid values log through the provided logger in both implementations", () => { + const logsA: string[] = []; + const logsB: string[] = []; + mjsImpl({ MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "bogus" }, (m) => logsA.push(m)); + tsImpl({ MAIN_SERVER_KEEPALIVE_TIMEOUT_MS: "bogus" }, (m) => logsB.push(m)); + assert.strictEqual(logsA.length, 1); + assert.deepStrictEqual(logsA, logsB); +}); diff --git a/tests/unit/minimax-m3-model-registry.test.ts b/tests/unit/minimax-m3-model-registry.test.ts index 820cbc77ca..2aae2b3a69 100644 --- a/tests/unit/minimax-m3-model-registry.test.ts +++ b/tests/unit/minimax-m3-model-registry.test.ts @@ -29,13 +29,11 @@ describe("MiniMax M3 model registration (#3110)", () => { assert.equal(m3.contextLength, 1_048_576); }); - it("opencode provider has minimax-m3-free with 1M context", () => { + it("opencode provider does NOT list minimax-m3-free (#6998 — delisted upstream, 401)", () => { const entry = REGISTRY.opencode; assert.ok(entry, "opencode registry entry must exist"); const m3 = entry.models.find((m) => m.id === "minimax-m3-free"); - assert.ok(m3, "minimax-m3-free must be in opencode models"); - assert.equal(m3.name, "MiniMax M3 Free"); - assert.equal(m3.contextLength, 1_048_576); + assert.equal(m3, undefined, "minimax-m3-free was delisted from OpenCode Zen's free tier (#6998)"); }); it("opencode-go provider has minimax-m3 with Claude targetFormat", () => { diff --git a/tests/unit/next-config.test.ts b/tests/unit/next-config.test.ts index 29821fa4ec..9281175217 100644 --- a/tests/unit/next-config.test.ts +++ b/tests/unit/next-config.test.ts @@ -288,6 +288,30 @@ test("turbopack.ignoreIssue suppresses the agentSkills over-bundling warning (#6 assert.match(String(agentSkillsRule.description), /Overly broad patterns/); }); +test("turbopack.ignoreIssue suppresses the compression module over-bundling warning (#7051)", async () => { + // open-sse/services/compression/ruleLoader.ts and + // .../engines/rtk/filterLoader.ts both define an identical getModuleDir() + // helper that walks up directories via path.resolve(anchor) + + // fs.existsSync(...) in a loop with a non-literal argument — the same + // class of dynamic-path fs access that #6582 suppressed for + // src/lib/agentSkills/**, but that narrow allowlist glob didn't cover this + // module, so the warning kept firing (610 times) for every entry point + // transitively importing the compression module. This guards the config + // shape so the suppression rule isn't silently dropped in a future edit. + const { default: nextConfig } = await loadNextConfig("ignore-issue-compression"); + const rules = nextConfig.turbopack?.ignoreIssue; + + assert.ok(Array.isArray(rules), "expected turbopack.ignoreIssue to be an array"); + const compressionRule = rules.find((rule) => + String(rule.path).includes("open-sse/services/compression") + ); + assert.ok( + compressionRule, + "expected an ignoreIssue rule targeting open-sse/services/compression/**" + ); + assert.match(String(compressionRule.description), /Overly broad patterns/); +}); + test("optimizePackageImports excludes the internal @omniroute/open-sse workspace (build-OOM guard)", async () => { // Regression guard: adding the internal `@omniroute/open-sse` workspace to // optimizePackageImports makes Next.js resolve its entire barrel at build diff --git a/tests/unit/noAuthProxyResolution.relayAuth.test.ts b/tests/unit/noAuthProxyResolution.relayAuth.test.ts new file mode 100644 index 0000000000..f838038a0f --- /dev/null +++ b/tests/unit/noAuthProxyResolution.relayAuth.test.ts @@ -0,0 +1,49 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { resolveAccountProxies } from "../../src/sse/services/noAuthProxyResolution.ts"; + +test("resolveAccountProxies preserves relayAuth for relay-type (vercel/deno/cloudflare) pool proxies", async () => { + const fakeVercelProxyRow = { + id: "proxy-1", + type: "vercel", + host: "my-relay-abc123.vercel.app", + port: 443, + username: null, + password: null, + notes: JSON.stringify({ relayAuth: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }), + }; + + const resolved = await resolveAccountProxies( + [{ fingerprint: "acct-1", proxyId: "proxy-1" }], + async (id) => (id === "proxy-1" ? fakeVercelProxyRow : null) + ); + + const proxy = resolved[0].proxy as unknown as { type?: string; relayAuth?: string }; + assert.equal(proxy?.type, "vercel"); + assert.equal( + proxy?.relayAuth, + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "relayAuth must survive resolveAccountProxies() for relay-type (vercel/deno/cloudflare) proxies" + ); +}); + +test("resolveAccountProxies leaves relayAuth absent for plain non-relay (socks5/http) pool proxies", async () => { + const fakeSocksProxyRow = { + id: "proxy-2", + type: "socks5", + host: "1.2.3.4", + port: 1080, + username: "u", + password: "p", + notes: JSON.stringify({ relayAuth: "should-not-leak-onto-non-relay-types" }), + }; + + const resolved = await resolveAccountProxies( + [{ fingerprint: "acct-2", proxyId: "proxy-2" }], + async (id) => (id === "proxy-2" ? fakeSocksProxyRow : null) + ); + + const proxy = resolved[0].proxy as unknown as { type?: string; relayAuth?: string }; + assert.equal(proxy?.type, "socks5"); + assert.equal(proxy?.relayAuth, undefined); +}); diff --git a/tests/unit/opencode-free-tier-catalog-stale-6998.test.ts b/tests/unit/opencode-free-tier-catalog-stale-6998.test.ts new file mode 100644 index 0000000000..c1d3eeae09 --- /dev/null +++ b/tests/unit/opencode-free-tier-catalog-stale-6998.test.ts @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { opencodeProvider } = await import( + "../../open-sse/config/providers/registry/opencode/index.ts" +); + +function modelIds(): string[] { + return (opencodeProvider.models ?? []).map((m) => m.id); +} + +const DELISTED_FREE_MODELS = [ + "minimax-m3-free", + "minimax-m2.5-free", + "ling-2.6-1t-free", + "trinity-large-preview-free", + "nemotron-3-super-free", + "qwen3.6-plus-free", +]; + +const LIVE_FREE_MODELS_MISSING_FROM_CATALOG = [ + "mimo-v2.5-free", + "hy3-free", + "nemotron-3-ultra-free", + "north-mini-code-free", +]; + +test("issue #6998: oc registry does not advertise delisted free-tier models", () => { + const ids = modelIds(); + for (const delisted of DELISTED_FREE_MODELS) { + assert.ok(!ids.includes(delisted), `oc registry still advertises delisted upstream model "${delisted}"`); + } +}); + +test("issue #6998: oc registry advertises the current live free-tier models", () => { + const ids = modelIds(); + for (const live of LIVE_FREE_MODELS_MISSING_FROM_CATALOG) { + assert.ok(ids.includes(live), `oc registry is missing live upstream free-tier model "${live}"`); + } +}); diff --git a/tests/unit/opencode-go-quota-no-zai.test.ts b/tests/unit/opencode-go-quota-no-zai.test.ts new file mode 100644 index 0000000000..b4775040fa --- /dev/null +++ b/tests/unit/opencode-go-quota-no-zai.test.ts @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { getOpenCodeGoUsage } from "../../open-sse/services/opencodeOllamaUsage.ts"; + +test("getOpenCodeGoUsage does not send the user's OpenCode Go API key to api.z.ai by default", async () => { + const originalFetch = globalThis.fetch; + const originalEnv = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; + delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; + + let calledHost: string | null = null; + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : input.toString(); + calledHost = new URL(url).host; + throw new Error(`unexpected outbound fetch to ${url}`); + }) as typeof fetch; + + try { + const result = await getOpenCodeGoUsage("sk-fake-opencode-go-key", undefined); + assert.notStrictEqual(calledHost, "api.z.ai"); + assert.strictEqual(calledHost, null); + assert.ok( + typeof result.message === "string" && result.message.length > 0, + "expected a descriptive message when no quota URL is configured" + ); + } finally { + globalThis.fetch = originalFetch; + if (originalEnv === undefined) delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; + else process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = originalEnv; + } +}); diff --git a/tests/unit/opencode-go-usage.test.ts b/tests/unit/opencode-go-usage.test.ts index 64e6357800..302a95f244 100644 --- a/tests/unit/opencode-go-usage.test.ts +++ b/tests/unit/opencode-go-usage.test.ts @@ -1,9 +1,25 @@ -import test from "node:test"; +import test, { after } from "node:test"; import assert from "node:assert/strict"; +// The OpenCode Go quota-by-API-key path is opt-in only (see #7022 — there is no +// working default quota endpoint, so OMNIROUTE_OPENCODE_GO_QUOTA_URL must be set +// explicitly by the operator). The module reads this env var once at import time, +// so it has to be set BEFORE the dynamic import below for the opt-in tests in this +// file (which simulate an operator who configured the URL) to exercise the fetch path. +const ORIGINAL_OPENCODE_GO_QUOTA_URL = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; +process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit"; + const usage = await import("../../open-sse/services/usage.ts"); const { USAGE_SUPPORTED_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); +after(() => { + if (ORIGINAL_OPENCODE_GO_QUOTA_URL === undefined) { + delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; + } else { + process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = ORIGINAL_OPENCODE_GO_QUOTA_URL; + } +}); + test("USAGE_SUPPORTED_PROVIDERS includes opencode-go", () => { assert.ok( (USAGE_SUPPORTED_PROVIDERS as string[]).includes("opencode-go"), @@ -298,7 +314,8 @@ test("getUsageForProvider returns message for invalid OpenCode Go API keys", asy })) as { message: string }; assert.equal( result.message, - "OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " + + "OpenCode Go API key is valid for chat/models but cannot read quota from the configured " + + "OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " + "Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping." ); } finally { @@ -342,7 +359,8 @@ test("getUsageForProvider returns message when OpenCode Go quota API returns 200 })) as { message: string }; assert.equal( result.message, - "OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " + + "OpenCode Go API key is valid for chat/models but cannot read quota from the configured " + + "OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " + "Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping." ); } finally { diff --git a/tests/unit/pack-artifact-entrypoint-closures.test.ts b/tests/unit/pack-artifact-entrypoint-closures.test.ts new file mode 100644 index 0000000000..94a037a68c --- /dev/null +++ b/tests/unit/pack-artifact-entrypoint-closures.test.ts @@ -0,0 +1,152 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { + APP_STAGING_ALLOWED_EXACT_PATHS, + PACK_ARTIFACT_REQUIRED_PATHS, +} from "../../scripts/build/pack-artifact-policy.ts"; + +// Generalization of pack-artifact-server-ws-closure.test.ts (#7065 class, 3rd recurrence: +// tls-options/3.8.41, head-response-guard VPS #7040 + npm #7065). assembleStandalone copies +// wrapper modules to the dist ROOT; the prepublish prune then deletes anything not in +// APP_STAGING_ALLOWED_EXACT_PATHS, and check:pack-artifact only fails for entries in +// PACK_ARTIFACT_REQUIRED_PATHS. The original test hardcoded ONE wrapper (server-ws.mjs) +// and ONE import form (static `from "./x"`). This suite derives the full closure from the +// sources of truth — EXTRA_MODULE_ENTRIES in assembleStandalone.mjs plus each wrapper's own +// imports (static, dynamic import() and require()) — so adding an import to ANY npm-shipped +// wrapper without updating both lists fails here instead of shipping a boot-crashing tarball. + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const ASSEMBLE = path.join(ROOT, "scripts", "build", "assembleStandalone.mjs"); +const BIN_ENTRY = path.join(ROOT, "bin", "omniroute.mjs"); + +interface WrapperEntry { + src: string; + dest: string; +} + +// EXTRA_MODULE_ENTRIES entries whose dest is a bare module filename at the dist root — +// these are the boot-path wrappers the prune can silently drop (the #7065 shape). +function distRootWrappers(): WrapperEntry[] { + const text = fs.readFileSync(ASSEMBLE, "utf8"); + const entries = [...text.matchAll(/src:\s*\[([^\]]+)\],?\s*dest:\s*\[([^\]]+)\]/gs)]; + const toPath = (segmentList: string) => + segmentList + .split(",") + .map((s) => s.trim().replace(/^"|"$/g, "")) + .filter(Boolean) + .join("/"); + return entries + .map((m) => ({ src: toPath(m[1]), dest: toPath(m[2]) })) + .filter((e) => !e.dest.includes("/") && /\.(mjs|cjs|js)$/.test(e.dest)); +} + +// Local sibling imports of a module: static `from "./x"`, dynamic `import("./x")`, +// and CommonJS `require("./x")`. The original test missed the dynamic form — server-ws +// boots dist/server.js via `await import("./server.js")`. +function localImports(filePath: string): string[] { + const src = fs.readFileSync(filePath, "utf8"); + const patterns = [ + /from\s+["']\.\/([^"']+)["']/g, + /import\(\s*["']\.\/([^"']+)["']\s*\)/g, + /require\(\s*["']\.\/([^"']+)["']\s*\)/g, + ]; + return [...new Set(patterns.flatMap((re) => [...src.matchAll(re)].map((m) => m[1])))]; +} + +/** Parent-relative specifiers (../) in a wrapper file — ALWAYS a packaging bug. */ +export function parentRelativeImports(src: string): string[] { + const patterns = [ + /from\s+["'](\.\.\/[^"']+)["']/g, + /import\(\s*["'](\.\.\/[^"']+)["']\s*\)/g, + /require\(\s*["'](\.\.\/[^"']+)["']\s*\)/g, + ]; + return [...new Set(patterns.flatMap((re) => [...src.matchAll(re)].map((m) => m[1])))]; +} + +// Wrappers that ship in the npm channel are exactly those whose dest survives the prune. +// Wrappers intentionally outside the npm tarball (e.g. healthcheck.mjs, Docker-only) are +// excluded: their imports live or die with them, consistently. +function npmShippedWrappers(): WrapperEntry[] { + return distRootWrappers().filter((e) => APP_STAGING_ALLOWED_EXACT_PATHS.includes(e.dest)); +} + +test("sanity: EXTRA_MODULE_ENTRIES parsing finds the known dist-root wrappers", () => { + const dests = distRootWrappers().map((e) => e.dest); + for (const known of ["server-ws.mjs", "peer-stamp.mjs", "head-response-guard.cjs"]) { + assert.ok(dests.includes(known), `parser lost known wrapper ${known}: got ${dests.join(", ")}`); + } + assert.ok(dests.length >= 7, `parsed only ${dests.length} dist-root wrappers`); +}); + +test("every local import of every npm-shipped wrapper survives the prune (allowlist)", () => { + for (const wrapper of npmShippedWrappers()) { + const srcPath = path.join(ROOT, wrapper.src); + assert.ok(fs.existsSync(srcPath), `EXTRA_MODULE_ENTRIES src missing on disk: ${wrapper.src}`); + const missing = localImports(srcPath).filter( + (f) => !APP_STAGING_ALLOWED_EXACT_PATHS.includes(f) + ); + assert.deepEqual( + missing, + [], + `${wrapper.dest}: add to APP_STAGING_ALLOWED_EXACT_PATHS: ${missing.join(", ")}` + ); + } +}); + +test("every local import of every npm-shipped wrapper is enforced by check:pack-artifact", () => { + for (const wrapper of npmShippedWrappers()) { + const missing = localImports(path.join(ROOT, wrapper.src)).filter( + (f) => !PACK_ARTIFACT_REQUIRED_PATHS.includes(`dist/${f}`) + ); + assert.deepEqual( + missing, + [], + `${wrapper.dest}: add dist/ to PACK_ARTIFACT_REQUIRED_PATHS: ${missing.join(", ")}` + ); + } +}); + +test("dynamic import() closure is covered (server-ws boots dist/server.js)", () => { + const serverWs = distRootWrappers().find((e) => e.dest === "server-ws.mjs"); + assert.ok(serverWs, "server-ws.mjs wrapper not found in EXTRA_MODULE_ENTRIES"); + const imports = localImports(path.join(ROOT, serverWs.src)); + assert.ok( + imports.includes("server.js"), + `dynamic import extraction broken — server.js not among: ${imports.join(", ")}` + ); +}); + +test("every bin/omniroute.mjs local import is enforced by check:pack-artifact", () => { + // The CLI boot path (bin/omniroute.mjs → bin/cli/*) is covered by allowlist PREFIXES, + // so a file vanishing from the tarball never fails the unexpected-paths check — only + // PACK_ARTIFACT_REQUIRED_PATHS makes its absence loud. Derive the requirement from + // the entrypoint's own imports. + const missing = localImports(BIN_ENTRY).filter( + (f) => !PACK_ARTIFACT_REQUIRED_PATHS.includes(`bin/${f}`) + ); + assert.deepEqual( + missing, + [], + `add bin/ to PACK_ARTIFACT_REQUIRED_PATHS: ${missing.join(", ")}` + ); +}); + +test("no npm-shipped wrapper uses a parent-relative (../) import — it escapes the package after the dist-root copy", () => { + // 2026-07-15 live incident: standalone-server-ws.mjs imported + // ../../src/shared/utils/runtimeTimeouts.ts (merged in #7191); copied to the dist + // root, the specifier resolved to node_modules/src/... OUTSIDE the package and + // every boot of the packed tarball crashed with ERR_MODULE_NOT_FOUND (#7065 + // class — caught by check:pack-boot). Wrapper dependencies must be SIBLINGS + // (./x.mjs) with their own EXTRA_MODULE_ENTRIES copy + pack allowlist entry. + for (const wrapper of npmShippedWrappers()) { + const escaping = parentRelativeImports(fs.readFileSync(path.join(ROOT, wrapper.src), "utf8")); + assert.deepEqual( + escaping, + [], + `${wrapper.src} has package-escaping imports: ${escaping.join(", ")} — extract to a sibling module instead` + ); + } +}); diff --git a/tests/unit/pack-artifact-policy.test.ts b/tests/unit/pack-artifact-policy.test.ts index a5f78e2235..9738342e7e 100644 --- a/tests/unit/pack-artifact-policy.test.ts +++ b/tests/unit/pack-artifact-policy.test.ts @@ -105,11 +105,14 @@ test("findMissingArtifactPaths flags missing root runtime files in the tarball", // alphabetically (bin/ < dist/ < scripts/ < src/), minus the paths present // above (dist/server.js, bin/omniroute.mjs, package.json, the postinstall scripts). assert.deepEqual(missingPaths, [ + "bin/cli/data-dir.mjs", "bin/cli/program.mjs", + "bin/cli/utils/storageKeyProvision.mjs", "bin/mcp-server.mjs", "bin/nodeRuntimeSupport.mjs", "dist/head-response-guard.cjs", "dist/http-method-guard.cjs", + "dist/main-server-timeouts.mjs", "dist/open-sse/services/compression/engines/rtk/filters/generic-output.json", "dist/open-sse/services/compression/rules/en/filler.json", "dist/peer-stamp.mjs", diff --git a/tests/unit/probe-6699-jules-executor-misroute.test.ts b/tests/unit/probe-6699-jules-executor-misroute.test.ts new file mode 100644 index 0000000000..a2072814f4 --- /dev/null +++ b/tests/unit/probe-6699-jules-executor-misroute.test.ts @@ -0,0 +1,40 @@ +// Probe for issue #6699 -- "Google Jules provider validation rejects a valid API key". +// +// A second reporter (MohammadMD1383) supplied screenshots showing that OmniRoute, when +// actually routing a chat-completion request for a saved "jules" connection, sends the +// request to https://api.openai.com/v1/chat/completions and surfaces OpenAI's own +// "Incorrect API key provided ... platform.openai.com" error -- even though the provider +// is displayed as JULES with target "jules/jules". This probe proves the executor-level +// root cause directly: getExecutor("jules") has no specialized executor and no REGISTRY +// entry, so DefaultExecutor's constructor silently falls back to PROVIDERS.openai, +// making buildUrl() return OpenAI's endpoint for a provider the user believes is Jules. +import test from "node:test"; +import assert from "node:assert/strict"; +import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts"; + +test("#6699: jules has no specialized executor (falls through to DefaultExecutor)", () => { + assert.equal(hasSpecializedExecutor("jules"), false); +}); + +test("#6699: a chat-completion request routed to provider 'jules' must not silently hit OpenAI's endpoint", () => { + // Desired behavior: the Jules provider (a cloud-agent, registered only in + // CLOUD_AGENT_PROVIDERS/staticModels, never in the chat REGISTRY) must not silently + // resolve to OpenAI's chat/completions endpoint when routed through the normal + // chat-completions executor path. getExecutor() now throws a clear, sanitized error + // for this narrow set of chat-unsupported cloud-agent providers instead of falling + // through to DefaultExecutor's `PROVIDERS.openai` fallback (which produced the + // "Incorrect API key provided ... platform.openai.com" error the reporter saw for a + // genuine Jules key). Before the fix, getExecutor("jules") returned a working + // executor whose buildUrl() resolved to OpenAI's endpoint -- this assertion FAILS on + // unfixed release/v3.8.49 code because no error is thrown at all. + assert.throws( + () => getExecutor("jules"), + (err) => { + assert.match(err.message, /cloud-agent provider/i); + assert.match(err.message, /does not support direct chat completions/i); + assert.equal(err.status, 400); + return true; + }, + "provider 'jules' must raise a clear error instead of silently inheriting OpenAI's base URL/config" + ); +}); diff --git a/tests/unit/probe-6835-cyclebreaker.test.ts b/tests/unit/probe-6835-cyclebreaker.test.ts new file mode 100644 index 0000000000..fda653124d --- /dev/null +++ b/tests/unit/probe-6835-cyclebreaker.test.ts @@ -0,0 +1,28 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +test("getDbInstance() caps the probe-failed/restore cycle at 3 attempts (#6835)", async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-6835-")); + process.env.DATA_DIR = tmpDir; + const sqliteFile = path.join(tmpDir, "storage.sqlite"); + const backupFile = `${sqliteFile}.probe-failed-1000000000000`; + fs.writeFileSync(backupFile, Buffer.from("not a real sqlite file, always fails to open")); + const core = await import("../../src/lib/db/core.ts"); + const errors: string[] = []; + for (let i = 0; i < 6; i++) { + try { + core.getDbInstance(); + errors.push("(no error)"); + break; + } catch (err: unknown) { + errors.push(err instanceof Error ? err.message : String(err)); + } + } + const abortIndex = errors.findIndex((e) => e.includes("Aborting startup")); + assert.notEqual(abortIndex, -1, "Expected the cap to trip; got: " + errors.join(" | ")); + assert.ok(abortIndex <= 4, "Expected cap by call #4; took until #" + abortIndex); + fs.rmSync(tmpDir, { recursive: true, force: true }); +}); diff --git a/tests/unit/probe-6835-oom-uncapped.test.ts b/tests/unit/probe-6835-oom-uncapped.test.ts new file mode 100644 index 0000000000..9b91101646 --- /dev/null +++ b/tests/unit/probe-6835-oom-uncapped.test.ts @@ -0,0 +1,58 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +test("getDbInstance() eventually caps a persistently-OOMing sql.js probe (#6835)", async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-6835-oom-")); + process.env.DATA_DIR = tmpDir; + const sqliteFile = path.join(tmpDir, "storage.sqlite"); + fs.mkdirSync(sqliteFile); // forces better-sqlite3/node:sqlite to fail synchronously (EISDIR-style) + await import("../../src/lib/db/adapters/driverFactory.ts"); + const core = await import("../../src/lib/db/core.ts"); + const fakeAdapter = { + driver: "sql.js" as const, + open: true, + name: sqliteFile, + prepare() { + throw new Error("out of memory"); + }, + exec() { + throw new Error("out of memory"); + }, + pragma() { + throw new Error("out of memory"); + }, + transaction(fn: (...a: unknown[]) => T) { + return fn; + }, + immediate() {}, + async backup() {}, + checkpoint() {}, + close() {}, + raw: null, + }; + ( + globalThis as unknown as { __omnirouteSqlJsAdapters: Map } + ).__omnirouteSqlJsAdapters = new Map([[sqliteFile, fakeAdapter]]); + const errors: string[] = []; + for (let i = 0; i < 8; i++) { + try { + core.getDbInstance(); + errors.push("(no error)"); + break; + } catch (err: unknown) { + errors.push(err instanceof Error ? err.message : String(err)); + } + } + const anyAborted = errors.some((e) => e.includes("Aborting startup")); + assert.ok( + anyAborted, + "Expected getDbInstance() to eventually give up with a terminal " + + "'Aborting startup'-style diagnostic after repeated OOM probe failures, the same way it " + + "already does for generic corruption (#6632). Instead every call re-threw an identical, " + + "uncapped OOM error:\n" + errors.map((e, i) => ` [${i}] ${e}`).join("\n") + ); + fs.rmSync(tmpDir, { recursive: true, force: true }); +}); diff --git a/tests/unit/probe-7134-claude-web-empty-error-body.test.ts b/tests/unit/probe-7134-claude-web-empty-error-body.test.ts new file mode 100644 index 0000000000..b62659616a --- /dev/null +++ b/tests/unit/probe-7134-claude-web-empty-error-body.test.ts @@ -0,0 +1,102 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { writeFile } from "node:fs/promises"; + +// Issue #7134 — claude-web reported "Claude Web API error (400) with no +// response body" even when Claude's upstream DID send a real JSON error body. +// +// Root cause: tlsFetchStreaming() streams the upstream response to a temp +// file via tls-client-node's `streamOutputPath` mode. For a non-SSE, +// non-2xx response, the native binding resolves with an EMPTY in-memory +// `body` field (it only populates `body` for its non-streaming mode) even +// though the real error bytes were already written to the temp file and +// even peeked (`looksLikeSse`) to decide the response wasn't SSE. The old +// code read the empty `r.body` instead of the file it just peeked, throwing +// away the real upstream error detail. +// +// This test injects a fake `client` (matching the `{ request }` shape +// tlsFetchStreaming already accepts for DI) that reproduces the exact +// tls-client-node contract under `streamOutputPath`: write bytes to the file, +// resolve with an empty `body`. No `--experimental-test-module-mocks` flag +// needed — this exercises the real, unmodified `tlsFetchStreaming` via +// dependency injection instead of module-mocking `tls-client-node`. + +const { tlsFetchStreaming } = await import("../../open-sse/services/claudeTlsClient.ts"); + +const REAL_CLAUDE_ERROR_BODY = JSON.stringify({ + type: "error", + error: { + type: "invalid_request_error", + message: "This conversation UUID does not exist or you do not have access to it.", + }, +}); + +function makeFakeClient(status: number, bodyOnFile: string) { + return { + request: async (_url: string, opts: Record) => { + const streamOutputPath = opts.streamOutputPath as string; + await writeFile(streamOutputPath, bodyOnFile); + return { + status, + headers: {}, + // tls-client-node does not populate `body` for streamed requests — + // this is the exact defect condition. + body: "", + cookies: {}, + text: async () => "", + json: async () => ({}), + bytes: async () => new Uint8Array(), + }; + }, + }; +} + +test("issue #7134: tlsFetchStreaming surfaces the real error body for a non-SSE 400 under stream:true", async () => { + const client = makeFakeClient(400, REAL_CLAUDE_ERROR_BODY); + + const result = await tlsFetchStreaming( + client, + "https://claude.ai/api/organizations/x/chat_conversations/y/completion", + { method: "POST" }, + "[DONE]", + null, + 5_000 + ); + + assert.equal(result.status, 400); + assert.equal(result.body, null); + assert.ok( + result.text && result.text.includes("does not exist or you do not have access to it"), + `expected the real Claude error body to be surfaced, got: ${JSON.stringify(result.text)}` + ); +}); + +test("issue #7134: tlsFetchStreaming still uses r.body when the native client DOES populate it", async () => { + const client = { + request: async (_url: string, opts: Record) => { + const streamOutputPath = opts.streamOutputPath as string; + await writeFile(streamOutputPath, "{}"); + return { + status: 403, + headers: {}, + body: "populated body from native client", + cookies: {}, + text: async () => "", + json: async () => ({}), + bytes: async () => new Uint8Array(), + }; + }, + }; + + const result = await tlsFetchStreaming( + client, + "https://claude.ai/api/organizations/x/chat_conversations/y/completion", + { method: "POST" }, + "[DONE]", + null, + 5_000 + ); + + assert.equal(result.status, 403); + assert.equal(result.text, "populated body from native client"); +}); diff --git a/tests/unit/provider-registry-qwen-vision.test.ts b/tests/unit/provider-registry-qwen-vision.test.ts index 9382027aed..1b2ddaa3f2 100644 --- a/tests/unit/provider-registry-qwen-vision.test.ts +++ b/tests/unit/provider-registry-qwen-vision.test.ts @@ -63,16 +63,17 @@ test("#2822 opencode-go/qwen3.6-plus deve ter supportsVision !== true", () => { ); }); -// #3328 — o oposto do #2822: MiniMax M3 (opencode) É multimodal (verificado -// empiricamente: descreve imagens base64 via o upstream opencode). Deve ter -// supportsVision: true para não ser barrado/strippado em requests com imagem. -test("#3328 opencode/minimax-m3-free deve ter supportsVision: true", () => { +// #3328 — o oposto do #2822: MiniMax M3 (opencode) era multimodal (verificado +// empiricamente: descrevia imagens base64 via o upstream opencode). #6998: +// minimax-m3-free foi deslistado do free tier da OpenCode Zen (401 "not +// supported") em 2026-07-14 e removido do catálogo estático — este teste +// agora confirma a remoção. +test("#6998 opencode/minimax-m3-free não deve mais estar registrado (deslistado upstream)", () => { const model = getModel("opencode", "minimax-m3-free"); - assert.ok(model, "minimax-m3-free deve estar registrado em opencode"); - assert.strictEqual( - model.supportsVision, - true, - "opencode/minimax-m3-free é multimodal — supportsVision deve ser true" + assert.equal( + model, + undefined, + "opencode/minimax-m3-free foi deslistado do free tier da OpenCode Zen (#6998)" ); }); diff --git a/tests/unit/provider-validation-web-cookie-auth007.test.ts b/tests/unit/provider-validation-web-cookie-auth007.test.ts index c53705af04..344b1e865b 100644 --- a/tests/unit/provider-validation-web-cookie-auth007.test.ts +++ b/tests/unit/provider-validation-web-cookie-auth007.test.ts @@ -1,15 +1,18 @@ import test from "node:test"; import assert from "node:assert/strict"; -// The validator probes the provider's /models endpoint via safeOutboundFetch → -// fetchWithTimeout, which binds globalThis.fetch at MODULE LOAD time. The mock MUST be -// installed BEFORE importing validation.ts — a late reassignment (inside a test) is -// ignored and the validator hits the real network instead. (That made the 401/403 -// assertions pass only by coincidence — live chatgpt.com returns 401/403 — while the -// 200 case failed.) A mutable `nextResponse` lets each test vary the probe result, and -// `fetchCalls` proves the mocked probe ran rather than the live network. +// The validator probes the provider's /models endpoint via validationRead → safeOutboundFetch +// → fetchWithTimeout, which reads `globalThis.fetch` dynamically at CALL time (#7058 — routed +// through the proxy-aware patched fetch instead of a bypassing directHttpsRequest). Importing +// validation.ts pulls in the proxy-patch module, which installs its own `globalThis.fetch` +// exactly once at import time — so the mock must be (re)installed AFTER the import, not before, +// or the patch silently clobbers it. A mutable `nextResponse` lets each test vary the probe +// result, and `fetchCalls` proves the mocked probe ran rather than the live network. let nextResponse: { status: number; body: string } = { status: 200, body: "{}" }; let fetchCalls = 0; + +const { validateWebCookieProvider } = await import("../../src/lib/providers/validation.ts"); + globalThis.fetch = (async () => { fetchCalls++; return new Response(nextResponse.body, { @@ -18,8 +21,6 @@ globalThis.fetch = (async () => { }); }) as typeof fetch; -const { validateWebCookieProvider } = await import("../../src/lib/providers/validation.ts"); - function mockFetch(status: number, body: string) { nextResponse = { status, body }; fetchCalls = 0; diff --git a/tests/unit/providers-yuanbao-web.test.ts b/tests/unit/providers-yuanbao-web.test.ts index 2feab63371..48477c83d4 100644 --- a/tests/unit/providers-yuanbao-web.test.ts +++ b/tests/unit/providers-yuanbao-web.test.ts @@ -71,6 +71,13 @@ async function readStreamText(res: Response): Promise { } test("missing hy_token cookie returns a 401 auth error", async () => { + // Hermetic: the 401 must come from the executor's own cookie validation, never + // from the real upstream — on GitHub-hosted runners the Tencent endpoint is + // unreachable and a live call turns this into a 71s 502 false-negative. + const original = globalThis.fetch; + globalThis.fetch = (async () => { + throw new Error("network disabled in this test — executor must reject before fetching"); + }) as typeof fetch; const exec = new YuanbaoWebExecutor(); const { response } = await exec.execute({ model: "deepseek-v3", @@ -84,6 +91,7 @@ test("missing hy_token cookie returns a 401 auth error", async () => { assert.match(body.error.message, /hy_user|hy_token|session cookie/); // Never leak stack traces. assert.ok(!body.error.message.includes("at /")); + globalThis.fetch = original; }); test("streaming request translates think/text events into OpenAI chunks", async () => { diff --git a/tests/unit/quota-card-grid-density-6815.test.ts b/tests/unit/quota-card-grid-density-6815.test.ts new file mode 100644 index 0000000000..bafc3d6b3b --- /dev/null +++ b/tests/unit/quota-card-grid-density-6815.test.ts @@ -0,0 +1,190 @@ +// #6815 — Provider Quota page horizontal density. +// +// #6815 changed QuotaCardGrid.tsx's per-group card grid from a +// single-column-only layout (`flex flex-col`) to one that packs multiple +// QuotaCards side by side on wide screens, instead of stacking every card +// vertically no matter how much horizontal space is available. +// +// That guarantee was only ever asserted *incidentally*, by two other guards +// that pinned the literal Tailwind token the #6815 implementation happened +// to use at the time (`sm:grid-cols-2` in +// tests/unit/quota-card-grid-mobile-7072.test.ts and +// tests/unit/quota-card-grid-horizontal-layout.test.ts). When PR #7027 +// migrated the component from a fixed breakpoint ladder +// (`grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4`) to a +// container-driven auto-fit template +// (`grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))]`), that literal +// token disappeared from the source and both guards were edited to stop +// asserting it — silently deleting the only coverage #6815 had. +// +// This guard re-establishes dedicated coverage for the #6815 density +// guarantee itself, decoupled from *how* the component achieves it. Instead +// of matching a specific class-name token, it simulates, from the shipped +// className(s), how many columns the per-group card grid would actually +// render at a wide container width — supporting both mechanisms seen in this +// component's history (a Tailwind breakpoint ladder, and a CSS auto-fit +// `minmax()` template) — and asserts that count is >1. Reverting to a single +// unconditional column (`grid-cols-1` with no responsive/auto-fit variants, +// or dropping the grid entirely for `flex flex-col`) must fail this guard, +// regardless of which mechanism produced the regression. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import ts from "typescript"; + +const COMPONENT_PATH = path.resolve( + import.meta.dirname, + "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx" +); + +/** + * Extract the string literal passed to `className={...}` (or `className="..."`) + * for every JSX `
` opening element in the component's source, in source + * order, via the TypeScript compiler API (not a hand-rolled regex — tracks + * the real AST so it can't be fooled by comments/whitespace). + */ +function extractDivClassNames(sourcePath: string): string[] { + const sourceText = fs.readFileSync(sourcePath, "utf8"); + const sourceFile = ts.createSourceFile( + sourcePath, + sourceText, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TSX + ); + const classNames: string[] = []; + + function visit(node: ts.Node) { + if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) { + const tagName = node.tagName.getText(sourceFile); + if (tagName === "div") { + for (const attr of node.attributes.properties) { + if (ts.isJsxAttribute(attr) && attr.name.getText(sourceFile) === "className") { + const init = attr.initializer; + if (init && ts.isStringLiteral(init)) { + classNames.push(init.text); + } else if ( + init && + ts.isJsxExpression(init) && + init.expression && + ts.isStringLiteral(init.expression) + ) { + classNames.push(init.expression.text); + } + } + } + } + } + ts.forEachChild(node, visit); + } + visit(sourceFile); + return classNames; +} + +// Tailwind's default min-width breakpoints (px). Unprefixed utilities apply +// at every width (breakpoint 0) and later/larger breakpoints win the cascade +// once their min-width is met, mirroring Tailwind's mobile-first source order. +const TAILWIND_BREAKPOINTS: Record = { + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + "2xl": 1536, +}; + +type ColumnRule = + | { breakpoint: number; kind: "fixed"; columns: number } + | { breakpoint: number; kind: "autofit"; trackPx: number }; + +/** + * Parse every `grid-cols-*` utility (optionally breakpoint-prefixed) found in + * a className string into a column rule, supporting both mechanisms this + * component has shipped with: + * - a fixed count, e.g. `grid-cols-2`, `md:grid-cols-3` + * - a CSS auto-fit template, e.g. + * `grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))]`, from which + * the minimum track width (in px) is extracted. + */ +function parseColumnRules(className: string): ColumnRule[] { + const rules: ColumnRule[] = []; + for (const token of className.split(/\s+/).filter(Boolean)) { + const prefixMatch = token.match(/^(?:([a-zA-Z0-9]+):)?grid-cols-(.+)$/); + if (!prefixMatch) continue; + const [, prefix, rest] = prefixMatch; + const breakpoint = prefix ? (TAILWIND_BREAKPOINTS[prefix] ?? 0) : 0; + + if (/^\d+$/.test(rest)) { + rules.push({ breakpoint, kind: "fixed", columns: parseInt(rest, 10) }); + continue; + } + + const autoFitMatch = rest.match(/^\[repeat\(auto-fit,\s*minmax\((.+),\s*1fr\)\)\]$/); + if (autoFitMatch) { + const trackPxMatches = [...autoFitMatch[1].matchAll(/(\d+)px/g)]; + if (trackPxMatches.length > 0) { + const trackPx = parseInt(trackPxMatches[trackPxMatches.length - 1][1], 10); + rules.push({ breakpoint, kind: "autofit", trackPx }); + } + } + } + return rules; +} + +/** + * Given a className string, estimate how many columns the grid renders at a + * given container/viewport width, by picking the widest matching breakpoint + * rule (Tailwind cascade) and resolving fixed vs. auto-fit tracks. Returns 1 + * (single column) when no `grid-cols-*` rule is present at all — e.g. a + * `flex flex-col` layout. + */ +function estimateColumnsAtWidth(className: string, widthPx: number): number { + const rules = parseColumnRules(className).filter((r) => r.breakpoint <= widthPx); + if (rules.length === 0) return 1; + const active = rules.reduce((best, r) => (r.breakpoint >= best.breakpoint ? r : best)); + if (active.kind === "fixed") return active.columns; + return Math.max(1, Math.floor(widthPx / active.trackPx)); +} + +// --- Self-test of the estimator against known-good and known-bad shapes --- +// (independent of the real component, so the simulation logic itself is +// pinned before it's trusted to judge the shipped source below). + +test("#6815 density estimator — breakpoint ladder resolves to multiple columns on a wide viewport", () => { + const className = "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3"; + assert.equal(estimateColumnsAtWidth(className, 1200), 3); + assert.equal(estimateColumnsAtWidth(className, 375), 1); +}); + +test("#6815 density estimator — auto-fit template resolves to multiple columns on a wide container", () => { + const className = "grid grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))] gap-3"; + assert.ok(estimateColumnsAtWidth(className, 1200) >= 2); + assert.equal(estimateColumnsAtWidth(className, 200), 1); +}); + +test("#6815 density estimator — single unconditional column stays at 1 column regardless of width", () => { + assert.equal(estimateColumnsAtWidth("grid grid-cols-1 gap-3", 1920), 1); +}); + +test("#6815 density estimator — flex column stack (no grid-cols) resolves to 1 column", () => { + assert.equal(estimateColumnsAtWidth("flex flex-col gap-3", 1920), 1); +}); + +// --- The actual regression guard, reading the shipped component source --- + +test("QuotaCardGrid (#6815) — per-group card grid renders multiple columns on a wide container", () => { + const classNames = extractDivClassNames(COMPONENT_PATH); + const cardGridClassName = classNames.find((c) => /\bgrid\b/.test(c) && /grid-cols-/.test(c)); + assert.ok( + cardGridClassName, + "expected to find a grid-based per-group card grid className (not a single-column flex stack)" + ); + + const columnsOnWideContainer = estimateColumnsAtWidth(cardGridClassName!, 1200); + assert.ok( + columnsOnWideContainer > 1, + `expected the per-group card grid to render more than 1 column at 1200px, got ${columnsOnWideContainer} ` + + `from className="${cardGridClassName}" — this is the #6815 density regression` + ); +}); diff --git a/tests/unit/quota-card-grid-horizontal-layout.test.ts b/tests/unit/quota-card-grid-horizontal-layout.test.ts index 9966034ba9..5e9fcd52ba 100644 --- a/tests/unit/quota-card-grid-horizontal-layout.test.ts +++ b/tests/unit/quota-card-grid-horizontal-layout.test.ts @@ -6,8 +6,11 @@ // the shipped JSX structure and grouping logic directly: // 1. Grouping still produces one header per distinct provider with the // correct account count ("N account(s)"). -// 2. The per-group card grid starts multi-column (`grid-cols-2`), not -// single-column, so cards fill horizontal space sooner. +// 2. The per-group card grid keeps a single-column mobile fallback +// (`grid-cols-1`) below the `sm:` breakpoint (restored by #7072 after +// PR #6815 dropped it and clipped labels on phone-width viewports), +// while still going multi-column (`sm:grid-cols-2`) from `sm:` up so +// cards fill horizontal space sooner on larger screens. // 3. Provider groups themselves flow into multiple columns on wide screens // (`columns-*`) instead of an unconditional vertical `flex flex-col` // stack. @@ -104,14 +107,14 @@ test("QuotaCardGrid (#3520) — outer container flows groups into multiple colum assert.notEqual(outerClassName, "flex flex-col gap-6"); }); -test("QuotaCardGrid (#3520) — per-group card grid starts multi-column (grid-cols-2), not single-column", () => { +test("QuotaCardGrid (#3520/#7072) — per-group card grid keeps a mobile grid-cols-1 fallback and goes multi-column from sm: up", () => { const classNames = extractDivClassNames(COMPONENT_PATH); const cardGridClassName = classNames.find( (c) => /\bgrid\b/.test(c) && /grid-cols-/.test(c) ); assert.ok(cardGridClassName, "expected to find the per-group card grid's className"); - assert.match(cardGridClassName!, /\bgrid-cols-2\b/); - assert.doesNotMatch(cardGridClassName!, /\bgrid-cols-1\b/); + assert.match(cardGridClassName!, /\bgrid-cols-1\b/); + assert.match(cardGridClassName!, /\bsm:grid-cols-2\b/); }); test("QuotaCardGrid (#3520) — early-returns null when there are no connections", () => { diff --git a/tests/unit/quota-card-grid-mobile-7072.test.ts b/tests/unit/quota-card-grid-mobile-7072.test.ts new file mode 100644 index 0000000000..aeede949a8 --- /dev/null +++ b/tests/unit/quota-card-grid-mobile-7072.test.ts @@ -0,0 +1,79 @@ +// #7072 — Provider Quota page card grid clipped on mobile. +// +// PR #6815 changed QuotaCardGrid.tsx's per-group card grid from +// `grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4` to +// `grid-cols-2 md:grid-cols-3 xl:grid-cols-4`, dropping the mobile (<768px) +// single-column fallback that every other card-grid in the dashboard still +// has (ProviderQuotaWidget.tsx, EvalsTab.tsx, MediaPageClient.tsx, +// SystemStorageTab.tsx). Forcing 2 columns even on phone widths squeezes +// each QuotaCard, and since QuotaCard's outer Card uses `overflow-hidden`, +// the overflowing button/label text is clipped instead of wrapping. +// +// This regression guard parses QuotaCardGrid.tsx's JSX via the TypeScript +// compiler API and asserts the per-group card grid's className restores an +// unprefixed `grid-cols-1` mobile fallback, while keeping the #6815 density +// gains (grid-cols-2 at `sm:` and up). + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import ts from "typescript"; + +const COMPONENT_PATH = path.resolve( + import.meta.dirname, + "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx" +); + +function extractDivClassNames(sourcePath: string): string[] { + const sourceText = fs.readFileSync(sourcePath, "utf8"); + const sourceFile = ts.createSourceFile( + sourcePath, + sourceText, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TSX + ); + const classNames: string[] = []; + + function visit(node: ts.Node) { + if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) { + const tagName = node.tagName.getText(sourceFile); + if (tagName === "div") { + for (const attr of node.attributes.properties) { + if (ts.isJsxAttribute(attr) && attr.name.getText(sourceFile) === "className") { + const init = attr.initializer; + if (init && ts.isStringLiteral(init)) { + classNames.push(init.text); + } else if ( + init && + ts.isJsxExpression(init) && + init.expression && + ts.isStringLiteral(init.expression) + ) { + classNames.push(init.expression.text); + } + } + } + } + } + ts.forEachChild(node, visit); + } + visit(sourceFile); + return classNames; +} + +test("QuotaCardGrid (#7072) — per-group card grid keeps a single-column mobile fallback", () => { + const classNames = extractDivClassNames(COMPONENT_PATH); + const cardGridClassName = classNames.find((c) => /\bgrid\b/.test(c) && /grid-cols-/.test(c)); + assert.ok(cardGridClassName, "expected to find the per-group card grid's className"); + + const tokens = cardGridClassName!.split(/\s+/); + const unprefixedGridCols = tokens.find((t) => /^grid-cols-\d+$/.test(t)); + assert.equal( + unprefixedGridCols, + "grid-cols-1", + `expected unprefixed grid-cols-1 (mobile fallback), got className="${cardGridClassName}"` + ); + assert.match(cardGridClassName!, /\bsm:grid-cols-2\b/, "expected sm:grid-cols-2 to be preserved"); +}); diff --git a/tests/unit/rehome-open-prs.test.ts b/tests/unit/rehome-open-prs.test.ts new file mode 100644 index 0000000000..5b5c7d23df --- /dev/null +++ b/tests/unit/rehome-open-prs.test.ts @@ -0,0 +1,56 @@ +// Guard for scripts/release/rehome-open-prs.mjs — the parallel-cycle PR re-home +// (generate-release Phase 0a.0b step 3). +// +// The script exists because `gh pr edit --base` fails SILENTLY (v3.8.42): it exits 0 +// while leaving the base untouched. The network side of that is verified by a read-back +// in the script itself; what is testable here is the decision — which PRs get retargeted +// and which are left alone. Getting that wrong in either direction is expensive: +// - retargeting a PR that was never on the frozen branch drags unrelated work into the cycle +// - skipping one strands a contributor on a branch the captain has taken over + +import test from "node:test"; +import assert from "node:assert/strict"; + +import { classify } from "../../scripts/release/rehome-open-prs.mjs"; + +const FROZEN = "release/v3.8.49"; +const NEXT = "release/v3.8.50"; + +test("retargets an open PR sitting on the frozen release branch", () => { + const { action } = classify( + { number: 1, baseRefName: FROZEN, isDraft: false }, + FROZEN, + NEXT + ); + assert.equal(action, "retarget"); +}); + +test("retargets a DRAFT on the frozen branch — a draft still needs a live base", () => { + const { action } = classify({ number: 2, baseRefName: FROZEN, isDraft: true }, FROZEN, NEXT); + assert.equal(action, "retarget"); +}); + +test("skips a PR already re-homed — the script must be idempotent for a resumed release", () => { + const { action, reason } = classify( + { number: 3, baseRefName: NEXT, isDraft: false }, + FROZEN, + NEXT + ); + assert.equal(action, "skip"); + assert.match(reason, /already re-homed/); +}); + +test("never touches a PR based on main — that is the release PR's own lane", () => { + const { action, reason } = classify({ number: 4, baseRefName: "main", isDraft: false }, FROZEN, NEXT); + assert.equal(action, "skip"); + assert.match(reason, /not the frozen branch/); +}); + +test("never touches a PR based on an older, already-shipped release", () => { + const { action } = classify( + { number: 5, baseRefName: "release/v3.8.47", isDraft: false }, + FROZEN, + NEXT + ); + assert.equal(action, "skip"); +}); diff --git a/tests/unit/shared/components/ProxyConfigModal.test.tsx b/tests/unit/shared/components/ProxyConfigModal.test.tsx index 12299359d1..a63ebe9801 100644 --- a/tests/unit/shared/components/ProxyConfigModal.test.tsx +++ b/tests/unit/shared/components/ProxyConfigModal.test.tsx @@ -389,3 +389,66 @@ describe("ProxyConfigModal custom registry saves", () => { ).toBe(false); }); }); + +describe("ProxyConfigModal test connection (saved proxy)", () => { + beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + fetchCalls = []; + }); + + afterEach(() => { + while (cleanupCallbacks.length > 0) { + cleanupCallbacks.pop()?.(); + } + document.body.innerHTML = ""; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it("includes proxyId when testing a saved SOCKS5 registry proxy so the server can load its stored credentials", async () => { + installFetchMock((url, init) => { + const method = String(init?.method || "GET").toUpperCase(); + if (method === "GET" && url === "/api/settings/proxies") { + return { + body: { + items: [ + { + id: "socks5-1", + name: "Geonode SOCKS5", + type: "socks5", + host: "proxy.geonode.io", + port: 12000, + username: "***", + password: "***", + source: "manual", + }, + ], + total: 1, + socks5Enabled: true, + }, + }; + } + if (url.startsWith("/api/settings/proxies/assignments?") && url.includes("scope=provider")) { + return { + body: { items: [{ proxyId: "socks5-1", scope: "provider", scopeId: "claude" }], total: 1 }, + }; + } + if (method === "POST" && url === "/api/settings/proxy/test") { + return { body: { success: true, publicIp: "1.2.3.4", latencyMs: 500 } }; + } + return defaultProxyConfigResponses(url) || { status: 404, body: {} }; + }); + + const { container } = await renderProxyConfigModal(); + await clickButton(container, "testConnection"); + await waitForCall((call) => call.method === "POST" && call.url === "/api/settings/proxy/test"); + + const testCall = fetchCalls.find( + (call) => call.method === "POST" && call.url === "/api/settings/proxy/test" + ); + expect(testCall).toBeTruthy(); + expect(testCall?.body?.proxyId).toBe("socks5-1"); + }, 20000); +}); diff --git a/tests/unit/standalone-server-ws-keepalive-timeout-7003.test.ts b/tests/unit/standalone-server-ws-keepalive-timeout-7003.test.ts new file mode 100644 index 0000000000..b69784738e --- /dev/null +++ b/tests/unit/standalone-server-ws-keepalive-timeout-7003.test.ts @@ -0,0 +1,77 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// #7003 — the RED/GREEN spec in main-server-keepalive-timeout-7003.test.ts proves +// getMainServerTimeoutConfig() raises keepAliveTimeout/headersTimeout above Node's +// unconfigured 5_000ms default, and that the original fix wired it into +// scripts/dev/run-next.mjs. But run-next.mjs only runs `npm run dev`/`npm start` +// from a source checkout. The server real end users run — `omniroute serve` +// (npm-installed CLI), Docker, and Electron — spawns the standalone Next build's +// server.js via scripts/dev/run-standalone.mjs, which prefers server-ws.mjs +// (built from scripts/dev/standalone-server-ws.mjs, copied byte-for-byte into +// dist/server-ws.mjs by scripts/build/assembleStandalone.mjs) over the bare +// server.js specifically because it wraps `http.createServer` with production +// behavior the bare server lacks (peer-IP stamping, method/HEAD guards, WS +// proxying, TLS). Before this fix, that wrapper left Node's http.Server +// keepAliveTimeout/headersTimeout at their unconfigured defaults, so the +// JetBrains AI Assistant reconnect bug reproduced by main-server-keepalive-timeout +// -7003.test.ts still hit the production entry point every real user runs. +// +// standalone-server-ws.mjs has top-level side effects (monkeypatches +// http.createServer, generates a random UUID, and unconditionally +// `await import("./server.js")` — a file that only exists in the assembled +// standalone output, not in the source tree) so it cannot be imported +// in-process. Guard the fix by inspecting the source, mirroring the pattern +// used for run-next.mjs in run-next-node-env.test.ts. +const here = path.dirname(fileURLToPath(import.meta.url)); +const source = fs.readFileSync( + path.resolve(here, "../../scripts/dev/standalone-server-ws.mjs"), + "utf8" +); + +test("standalone-server-ws.mjs imports getMainServerTimeoutConfig", () => { + // The wrapper must import the SIBLING ./main-server-timeouts.mjs — a + // ../../src/... import escapes the package after the dist-root copy and + // crashed every packed boot (2026-07-15, #7065 class). Parity with the + // canonical runtimeTimeouts.ts is guarded by main-server-timeouts-parity.test.ts. + assert.match( + source, + /import\s*\{\s*getMainServerTimeoutConfig\s*\}\s*from\s*["']\.\/main-server-timeouts\.mjs["']/, + "expected the production server wrapper to import getMainServerTimeoutConfig " + + "from its shipped sibling module (./main-server-timeouts.mjs)" + ); +}); + +test("standalone-server-ws.mjs applies keepAliveTimeout/headersTimeout to the wrapped server", () => { + assert.match( + source, + /server\.keepAliveTimeout\s*=\s*\w*[Tt]imeouts?\.keepAliveTimeoutMs/, + "expected the wrapped server object to have keepAliveTimeout set from getMainServerTimeoutConfig()" + ); + assert.match( + source, + /server\.headersTimeout\s*=\s*\w*[Tt]imeouts?\.headersTimeoutMs/, + "expected the wrapped server object to have headersTimeout set from getMainServerTimeoutConfig()" + ); +}); + +test("keepAliveTimeout/headersTimeout are applied inside createServerWithResponsesWs, before the server is returned", () => { + const factoryIdx = source.search(/function createServerWithResponsesWs/); + const keepAliveIdx = source.search(/server\.keepAliveTimeout\s*=/); + const returnIdx = source.search(/return server;/); + + assert.ok(factoryIdx !== -1, "expected createServerWithResponsesWs to exist"); + assert.ok(keepAliveIdx !== -1, "expected a server.keepAliveTimeout assignment to exist"); + assert.ok(returnIdx !== -1, "expected the wrapped server to be returned"); + assert.ok( + keepAliveIdx > factoryIdx, + "timeout wiring must happen inside createServerWithResponsesWs" + ); + assert.ok( + keepAliveIdx < returnIdx, + "timeout wiring must happen before the server object is returned to the caller" + ); +}); diff --git a/tests/unit/stream-utilities.test.ts b/tests/unit/stream-utilities.test.ts index 0f3404869c..33a1b2bdd7 100644 --- a/tests/unit/stream-utilities.test.ts +++ b/tests/unit/stream-utilities.test.ts @@ -133,7 +133,16 @@ test("createPassthroughStreamWithLogger synthesizes reasoning summary events fro assert.match(result, /event: response\.output_item\.done/); }); -test("createPassthroughStreamWithLogger shows a placeholder for encrypted reasoning items", async () => { +// Reconciles #7095 (xz-dev — chat clients never saw ANY signal that Codex was +// reasoning when the upstream Responses API exposed only encrypted private +// reasoning) with #7176 (JxnLexn — mutating `item.summary` with a fabricated +// placeholder corrupted the response item forwarded downstream, discarding the +// `encrypted_content` shape Codex needs for follow-up requests). Both goals are +// satisfied simultaneously: the client-facing synthetic delta/part events still +// carry the placeholder text, but the forwarded `response.output_item.done` +// payload is untouched — `encrypted_content` survives and no `summary` is +// fabricated onto the wire item. +test("createPassthroughStreamWithLogger shows a placeholder for encrypted reasoning items without mutating the forwarded item", async () => { const transform = createPassthroughStreamWithLogger( "codex", null, @@ -169,12 +178,77 @@ test("createPassthroughStreamWithLogger shows a placeholder for encrypted reason result += decoder.decode(value); } + // #7095: chat clients still see the placeholder via the synthetic events. assert.match(result, /event: response\.reasoning_summary_text\.delta/); assert.match(result, /Codex is reasoning/); - assert.match(result, /"summary":\[\{"type":"summary_text","text":"Codex is reasoning/); + + // #7176: the forwarded response.output_item.done payload is untouched — + // encrypted_content survives intact and no fabricated `summary` is present. + assert.match(result, /"encrypted_content":"enc_opaque_state"/); + assert.doesNotMatch(result, /"summary":/); assert.match(result, /event: response\.output_item\.done/); }); +// Companion guard for the test above. The output_item.done line is echoed +// verbatim, so a re-introduced `item.summary` mutation would NOT surface there. +// It does surface here: the reasoning item is captured into +// passthroughResponsesOutputItems and re-serialized into the response.completed +// snapshot when upstream sends an empty `output` (store: false). This is the +// path that actually fails if the mutation comes back — it is what makes the +// #7176 half of the reconciliation enforceable rather than incidental. +test("createPassthroughStreamWithLogger backfills completed output with encrypted reasoning unmutated", async () => { + const transform = createPassthroughStreamWithLogger( + "codex", + null, + null, + "gpt-5.5-low", + null, + null, + null, + null, + null, + "openai-responses" + ); + + const writer = transform.writable.getWriter(); + await writer.write( + new TextEncoder().encode( + [ + "event: response.output_item.done", + 'data: {"type":"response.output_item.done","response_id":"resp_reasoning_3","output_index":0,"item":{"id":"rs_resp_reasoning_3_0","type":"reasoning","encrypted_content":"enc_opaque_state"}}', + "", + "event: response.completed", + 'data: {"type":"response.completed","response":{"id":"resp_reasoning_3","model":"gpt-5.5-low","status":"completed","output":[],"usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}}', + "", + ].join("\n") + ) + ); + await writer.close(); + + const reader = transform.readable.getReader(); + const decoder = new TextDecoder(); + let result = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + result += decoder.decode(value); + } + + // The placeholder still reached the client (#7095). + assert.match(result, /event: response\.reasoning_summary_text\.delta/); + assert.match(result, /Codex is reasoning/); + + // The item re-serialized into the completed snapshot carries the original + // encrypted_content and never a fabricated summary (#7176). + const completed = result + .split(/\n\n+/) + .find((block) => block.includes('"type":"response.completed"')); + assert.ok(completed, "expected a response.completed event on the wire"); + assert.match(completed, /"encrypted_content":"enc_opaque_state"/); + assert.doesNotMatch(completed, /"summary":/); +}); + test("createPassthroughStreamWithLogger backfills completed output from function_call arguments events", async () => { const transform = createPassthroughStreamWithLogger( "codex", diff --git a/tests/unit/sync-next-cycle.test.ts b/tests/unit/sync-next-cycle.test.ts index 8fdbb8b429..fb5ecfdf15 100644 --- a/tests/unit/sync-next-cycle.test.ts +++ b/tests/unit/sync-next-cycle.test.ts @@ -123,3 +123,31 @@ test("i18n resync also propagates the FINALIZED [prevVersion] section into the m "syncs the shipped (finalized) section — without this all 42 mirrors keep it as TBD" ); }); + +// WS0.3 (v3.8.49 quality plan): the captain's sync-back push is the one write path +// with NO CI gate — the merged tree must pass release-green --quick BEFORE the push, +// or the whole PR queue inherits a red tip (G1). --skip-green-gate is the documented +// emergency escape hatch (pre-existing tip reds verified by hand). + +test("greenGateArgs returns the quick release-green command by default", async () => { + const { greenGateArgs } = await import("../../scripts/release/sync-next-cycle.mjs"); + assert.deepEqual(greenGateArgs(["node", "script", "3.8.49"]), [ + "scripts/quality/validate-release-green.mjs", + "--quick", + ]); +}); + +test("greenGateArgs returns null only with the explicit --skip-green-gate flag", async () => { + const { greenGateArgs } = await import("../../scripts/release/sync-next-cycle.mjs"); + assert.equal(greenGateArgs(["node", "script", "3.8.49", "--skip-green-gate"]), null); + assert.notEqual(greenGateArgs(["node", "script", "3.8.49", "--other"]), null); +}); + +test("sync-next-cycle gates the push on release-green (source guard)", () => { + const src = readFileSync(SCRIPT_PATH, "utf8"); + const mainIdx = src.indexOf("function main()"); + const gateCallIdx = src.indexOf("greenGateArgs(process.argv)", mainIdx); + const pushIdx = src.indexOf('git(["push", "origin", BRANCH]'); + assert.ok(gateCallIdx > mainIdx, "main() must call greenGateArgs(process.argv)"); + assert.ok(pushIdx > gateCallIdx, "the release-green gate must run BEFORE the push"); +}); diff --git a/tests/unit/ui/CliAgentsPage.test.tsx b/tests/unit/ui/CliAgentsPage.test.tsx index 82f4a5205f..257f5e4149 100644 --- a/tests/unit/ui/CliAgentsPage.test.tsx +++ b/tests/unit/ui/CliAgentsPage.test.tsx @@ -54,12 +54,18 @@ const { default: CliAgentsPageClient } = await import( // ── Fixtures ────────────────────────────────────────────────────────────────── -/** 6 agent tool ids from the catalog (§3.2 of plan-14) */ +/** + * Agent tool ids from the catalog (§3.2 of plan-14, category: "agent" in + * src/shared/constants/cliTools.ts). "omp" and "letta" were added to the + * catalog after plan-14 shipped, bringing the count from 6 to 8. + */ const AGENT_IDS = [ "openclaw", "hermes-agent", "goose", "interpreter", + "omp", + "letta", "warp", "agent-deck", ] as const; @@ -144,9 +150,9 @@ describe("CliAgentsPageClient", () => { expect(container.textContent).toContain("pageTitle"); }, 15000); - it("2. renders exactly 6 agent tool cards", async () => { + it("2. renders exactly 8 agent tool cards", async () => { const container = await renderPage(); - expect(countAgentCards(container)).toBe(6); + expect(countAgentCards(container)).toBe(8); }, 15000); it("3. search filter — 'hermes' shows 1 card (hermes-agent)", async () => { diff --git a/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx b/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx new file mode 100644 index 0000000000..5d58e13640 --- /dev/null +++ b/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +// Regression probe for issue #7151: OpenRouter (and every other +// `passthroughModels` provider — requesty, dgrid, agentrouter, charm-hyper, +// etc.) never appears in the Hermes Agent role model picker. +// +// Root cause: derives a passthrough provider's model list +// from the `modelAliases` prop (ModelSelectModal.tsx groupedModels → +// buildPassthroughAliasModels(modelAliases, providerId)). When `modelAliases` +// is `{}` (the component default), that helper returns `[]` and the provider +// group is skipped entirely — see modelSelectModalHelpers.ts. Every sibling +// CLI tool card (Codex, Claude, Cline, Kilo, Droid, OpenClaw, Antigravity) +// fetches `/api/models/alias` and passes the result through, but +// HermesAgentToolCard never does, so OpenRouter's managed-available-model +// aliases (synced automatically after the connection is tested — see +// syncManagedAvailableModelAliases in src/lib/providerModels/managedAvailableModels.ts) +// are invisible to it. + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const CARD_PATH = resolve( + __dirname, + "../../../src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx" +); + +describe("HermesAgentToolCard model alias wiring (#7151)", () => { + const source = readFileSync(CARD_PATH, "utf8"); + + it("declares modelAliases state", () => { + expect(source).toMatch(/const \[modelAliases, setModelAliases\] = useState\(\{\}\)/); + }); + + it("fetches /api/models/alias when expanded", () => { + expect(source).toContain('fetch("/api/models/alias")'); + }); + + it("passes modelAliases prop to ModelSelectModal", () => { + // Regression guard: this prop is what unlocks passthrough provider groups + // (OpenRouter, Requesty, DGrid, AgentRouter, Charm Hyper, ...) in the + // Hermes Agent role picker. Without it, OpenRouter is silently absent + // from the "Select" modal for every role (Default, Delegation, ...). + expect(source).toMatch(/modelAliases=\{modelAliases\}/); + }); +}); diff --git a/tests/unit/ui/agent-bridge-server-card-a11y.test.tsx b/tests/unit/ui/agent-bridge-server-card-a11y.test.tsx index 9db239e614..b1892179c6 100644 --- a/tests/unit/ui/agent-bridge-server-card-a11y.test.tsx +++ b/tests/unit/ui/agent-bridge-server-card-a11y.test.tsx @@ -2,7 +2,7 @@ * a11y tests for AgentBridgeServerCard — each action button must have aria-label. * Uses source-text inspection (no JSDOM render needed) for the structural assertion. */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; diff --git a/tests/unit/ui/cli-tools-no-mitm-tab.test.tsx b/tests/unit/ui/cli-tools-no-mitm-tab.test.tsx index bce2f28fcc..cba7c99ffe 100644 --- a/tests/unit/ui/cli-tools-no-mitm-tab.test.tsx +++ b/tests/unit/ui/cli-tools-no-mitm-tab.test.tsx @@ -4,7 +4,7 @@ * * Uses source-text inspection — no JSDOM render needed. */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; diff --git a/tests/unit/ui/compressionHub-context-editing.test.tsx b/tests/unit/ui/compressionHub-context-editing.test.tsx index 88fee2e490..6abeb667b6 100644 --- a/tests/unit/ui/compressionHub-context-editing.test.tsx +++ b/tests/unit/ui/compressionHub-context-editing.test.tsx @@ -139,8 +139,11 @@ describe("CompressionHub — Context Editing", () => { }); await flush(); + // CompressionHub deliberately does NOT use useTranslations (see the + // hydration note at the top of CompressionHub.tsx) — its strings are + // literal English text, exactly like EngineConfigPage. const text = container.textContent ?? ""; - expect(text).toContain("Compressão delegada ao provedor"); + expect(text).toContain("Provider-delegated compression"); expect(text).toContain("Context Editing (Claude)"); }); @@ -156,8 +159,8 @@ describe("CompressionHub — Context Editing", () => { await flush(); const text = container.textContent ?? ""; - expect(text).toContain("apenas para Claude"); - expect(text).toContain("não reescrevemos a mensagem"); + expect(text).toContain("available for Claude (Anthropic) only"); + expect(text).toContain("we do not rewrite the message"); }); it("PUTs contextEditing: { enabled: true } when the toggle is flipped on", async () => { diff --git a/tests/unit/ui/compressionHub.test.tsx b/tests/unit/ui/compressionHub.test.tsx index 166c0fef04..27d380d3f0 100644 --- a/tests/unit/ui/compressionHub.test.tsx +++ b/tests/unit/ui/compressionHub.test.tsx @@ -111,28 +111,13 @@ async function flush() { // ── Tests ───────────────────────────────────────────────────────────────── describe("CompressionHub", () => { - it("renders the master switch, mode selector, and the layered pipeline", async () => { - setupFetchMock({ enabled: true, mode: "stacked", pipeline: [{ engine: "rtk" }] }); - const { default: CompressionHub } = - await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub"); - - let container!: HTMLElement; - await act(async () => { - container = mountInContainer(); - }); - await flush(); - - const text = container.textContent ?? ""; - expect(text).toContain("Compression Hub"); - expect(text).toContain("Token Saver"); - expect(text).toContain("Stacked"); - // Active pipeline engine (from the default combo) renders - expect(text).toContain("RTK"); - // Inactive engines from the catalog render too - expect(text).toContain("Caveman"); - // Active-pipeline callout shows when enabled && stacked - expect(text).toContain("Layer pipeline is active"); - }); + // NOTE: the master Token Saver toggle, mode selector, and layered-pipeline + // preview this describe block used to assert on were removed by the Phase 2 + // Hub redesign (see the "Phase 2" comment at the top of CompressionHub.tsx — + // the Hub is now a thin overview with just an active-profile selector + the + // Context Editing toggle). That redesign, including the explicit assertion + // that the master toggle/mode selector/reorder buttons no longer render, is + // covered by compressionHub-active-selector.test.tsx. it("INVARIANT #1: no per-layer control issues a PUT/POST to /api/context/combos/default", { timeout: 20000 }, async () => { const comboWrites: { method: string }[] = []; @@ -186,22 +171,6 @@ describe("CompressionHub", () => { expect(comboWrites).toHaveLength(0); }); - - it("shows the activation warning when Token Saver is off", async () => { - setupFetchMock({ enabled: false, mode: "off", pipeline: [] }); - const { default: CompressionHub } = - await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub"); - - let container!: HTMLElement; - await act(async () => { - container = mountInContainer(); - }); - await flush(); - - const text = container.textContent ?? ""; - expect(text).toContain("Enable Token Saver"); - expect(text).toContain("only run in Stacked mode"); - }); }); describe("CompressionCombosPageClient", () => { diff --git a/tests/unit/ui/conversation-tab-separators.test.tsx b/tests/unit/ui/conversation-tab-separators.test.tsx index 7faad9b6b1..5723955a56 100644 --- a/tests/unit/ui/conversation-tab-separators.test.tsx +++ b/tests/unit/ui/conversation-tab-separators.test.tsx @@ -3,7 +3,7 @@ * Validates the rendering logic: both sections appear when both request/response have turns; * only CONTEXT HISTORY appears when response is empty. */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { normalizeConversation } from "../../../src/mitm/inspector/conversationNormalizer.ts"; import type { InterceptedRequest } from "../../../src/mitm/inspector/types.ts"; diff --git a/tests/unit/ui/conversation-tab.test.tsx b/tests/unit/ui/conversation-tab.test.tsx index 62d405258e..92f8ab213c 100644 --- a/tests/unit/ui/conversation-tab.test.tsx +++ b/tests/unit/ui/conversation-tab.test.tsx @@ -1,7 +1,7 @@ /** * Tests for ConversationTab — normalizeConversation + chat bubble rendering logic */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { normalizeConversation } from "../../../src/mitm/inspector/conversationNormalizer.ts"; import type { InterceptedRequest } from "../../../src/mitm/inspector/types.ts"; diff --git a/tests/unit/ui/historic-session-banner.test.tsx b/tests/unit/ui/historic-session-banner.test.tsx index 910178514d..fb27927710 100644 --- a/tests/unit/ui/historic-session-banner.test.tsx +++ b/tests/unit/ui/historic-session-banner.test.tsx @@ -1,7 +1,7 @@ /** * Tests for HistoricSessionBanner — render with sessionName/null + backToLive callback */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; // Pure logic tests — no DOM needed (no next-intl in node:test runner) diff --git a/tests/unit/ui/home-topology-hidden-4596.test.tsx b/tests/unit/ui/home-topology-hidden-4596.test.tsx index cdb3a64662..2c3ba169b6 100644 --- a/tests/unit/ui/home-topology-hidden-4596.test.tsx +++ b/tests/unit/ui/home-topology-hidden-4596.test.tsx @@ -19,6 +19,14 @@ describe("home topology hidden networking (#4596)", () => { globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; websocketMock.mockClear(); + // useLiveDashboard runs a runtime handshake (GET /api/v1/ws?handshake=1) to + // discover the public WS URL before it opens the socket, so it never + // connects to the hardcoded default and then flaps to the public URL. Stub + // it to fail fast instead of letting the real global fetch hit the network. + vi.stubGlobal( + "fetch", + vi.fn(() => Promise.reject(new Error("network unavailable in test"))) + ); vi.stubGlobal( "WebSocket", class WebSocketMock { @@ -59,9 +67,14 @@ describe("home topology hidden networking (#4596)", () => { expect(websocketMock).not.toHaveBeenCalled(); }); - it("opens a WebSocket when the topology section is enabled", () => { - act(() => { + it("opens a WebSocket when the topology section is enabled", async () => { + await act(async () => { root!.render(); + // Let the handshake fetch's rejection propagate through .catch()/.finally() + // so `wsUrlResolved` flips to true and the connect effect re-runs. + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); }); expect(websocketMock).toHaveBeenCalledTimes(1); }); diff --git a/tests/unit/ui/memories-tab.test.tsx b/tests/unit/ui/memories-tab.test.tsx index cb56eaf143..845e1e4d7e 100644 --- a/tests/unit/ui/memories-tab.test.tsx +++ b/tests/unit/ui/memories-tab.test.tsx @@ -292,18 +292,27 @@ describe("MemoriesTab", () => { }); it("calls DELETE when delete confirmed", async () => { - const mockFetch = vi.fn(); - mockFetch.mockResolvedValueOnce({ - ok: true, - json: async () => ({ - data: MOCK_MEMORIES, - total: 2, - totalPages: 1, - stats: { total: 2, tokensUsed: 0, hitRate: 0, cacheStats: { hits: 0, misses: 0 } }, - }), + // MemoriesTab fires two independent fetches on mount: an immediate health + // check (/api/memory/health) and a 300ms-debounced memories list fetch + // (/api/memory?...). A call-order-dependent mock (mockResolvedValueOnce + + // fallback) is fragile here because the health check resolves first and + // would consume the "once" response meant for the list. Key off the URL + // instead, like the rest of this file's fetch mocks do. + const mockFetch = vi.fn((url: string) => { + if (typeof url === "string" && url.startsWith("/api/memory?")) { + return Promise.resolve({ + ok: true, + json: async () => ({ + data: MOCK_MEMORIES, + total: 2, + totalPages: 1, + stats: { total: 2, tokensUsed: 0, hitRate: 0, cacheStats: { hits: 0, misses: 0 } }, + }), + }); + } + return Promise.resolve({ ok: true, json: async () => ({}) }); }); - mockFetch.mockResolvedValue({ ok: true, json: async () => ({}) }); - globalThis.fetch = mockFetch; + globalThis.fetch = mockFetch as unknown as typeof fetch; const { default: MemoriesTab } = await import( "../../../src/app/(dashboard)/dashboard/memory/components/tabs/MemoriesTab" ); diff --git a/tests/unit/ui/model-select-modal-hidden-models-7156.test.tsx b/tests/unit/ui/model-select-modal-hidden-models-7156.test.tsx new file mode 100644 index 0000000000..957f033a7f --- /dev/null +++ b/tests/unit/ui/model-select-modal-hidden-models-7156.test.tsx @@ -0,0 +1,68 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import ModelSelectModal from "@/shared/components/ModelSelectModal"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +const roots: Array<{ root: ReturnType; el: HTMLDivElement }> = []; + +async function render(props: React.ComponentProps): Promise { + const el = document.createElement("div"); + document.body.appendChild(el); + const root = createRoot(el); + await act(async () => { + root.render(); + }); + roots.push({ root, el }); + return el; +} + +beforeEach(() => { + (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/api/combos")) return new Response(JSON.stringify({ combos: [] }), { status: 200 }); + if (url.includes("/api/provider-nodes")) return new Response(JSON.stringify({ nodes: [] }), { status: 200 }); + if (url.includes("/api/provider-models")) { + return new Response( + JSON.stringify({ + models: { + requesty: [ + { id: "visible-model-1", name: "Visible Model", source: "imported" }, + { id: "hidden-model-1", name: "Hidden Model", source: "imported", isHidden: true }, + ], + }, + modelCompatOverrides: [], + }), + { status: 200 } + ); + } + return new Response(JSON.stringify({}), { status: 200 }); + }) + ); +}); + +afterEach(() => { + for (const { root, el } of roots.splice(0)) { act(() => root.unmount()); el.remove(); } + vi.unstubAllGlobals(); + vi.clearAllMocks(); +}); + +describe("ModelSelectModal hidden-model filtering (#7156)", () => { + it("does not list a custom model explicitly flagged isHidden:true", async () => { + const el = await render({ + isOpen: true, onClose: vi.fn(), onSelect: vi.fn(), + activeProviders: [{ provider: "requesty", id: "conn-1" }], + modelAliases: {}, title: "Add model to combo", + }); + await act(async () => { await new Promise((r) => setTimeout(r, 0)); }); + expect(el.textContent).toContain("Visible Model"); + expect(el.textContent).not.toContain("Hidden Model"); + }); +}); diff --git a/tests/unit/ui/playground-build-tab.test.tsx b/tests/unit/ui/playground-build-tab.test.tsx index c954679818..2699aa4c53 100644 --- a/tests/unit/ui/playground-build-tab.test.tsx +++ b/tests/unit/ui/playground-build-tab.test.tsx @@ -64,6 +64,52 @@ function renderBuildTab(config = BASE_CONFIG): HTMLDivElement { return el; } +// ── BuildWizard navigation helpers ────────────────────────────────────────── +// +// BuildTab now renders behind a 3-step wizard (BuildWizard.tsx): +// step 1 — pick a mode (Tools / JSON / Both) +// step 2 — configure tools and/or the JSON schema, depending on the mode +// step 3 — run + toolbar badges + prompt textarea +// +// next-intl is mocked as a key pass-through above, so every translated label +// renders as its raw i18n key (e.g. "nextButton", "modeToolsTitle"). + +type BuildMode = "tools" | "json" | "both"; + +function clickNext(el: HTMLDivElement): void { + const nextBtn = Array.from(el.querySelectorAll("button")).find((b) => + b.textContent?.includes("nextButton"), + ) as HTMLButtonElement; + act(() => { + nextBtn.click(); + }); +} + +function selectMode(el: HTMLDivElement, mode: BuildMode): void { + // Step 1's default mode is already "tools" — only click a mode card when a + // different mode is required. + if (mode === "tools") return; + const key = mode === "json" ? "modeJsonTitle" : "modeBothTitle"; + const card = Array.from(el.querySelectorAll("button")).find((b) => + b.textContent?.includes(key), + ) as HTMLButtonElement; + act(() => { + card.click(); + }); +} + +/** Drive the wizard from step 1 to step 2, selecting `mode` along the way. */ +function goToStep2(el: HTMLDivElement, mode: BuildMode = "tools"): void { + selectMode(el, mode); + clickNext(el); +} + +/** Drive the wizard from step 1 all the way to step 3 (run screen). */ +function goToStep3(el: HTMLDivElement, mode: BuildMode = "tools"): void { + goToStep2(el, mode); + clickNext(el); +} + afterEach(() => { for (const { root, el } of containers) { act(() => root.unmount()); @@ -76,28 +122,34 @@ afterEach(() => { describe("BuildTab", () => { it("renders Run button", () => { const el = renderBuildTab(); - const runBtn = el.querySelector("[class*='bg-primary']"); - expect(runBtn?.textContent).toContain("runLabel"); + goToStep3(el); + const runBtn = Array.from(el.querySelectorAll("button")).find((b) => + b.textContent?.includes("runButton"), + ); + expect(runBtn).not.toBeUndefined(); }); it("renders Function calling section", () => { const el = renderBuildTab(); - expect(el.textContent).toContain("Function calling"); + goToStep2(el, "tools"); + expect(el.textContent).toContain("toolsLabel"); expect(el.textContent).toContain("Add tool"); }); it("renders Structured output section", () => { const el = renderBuildTab(); - expect(el.textContent).toContain("Structured output"); + goToStep2(el, "json"); + expect(el.textContent).toContain("structuredOutputLabel"); expect(el.textContent).toContain("JSON mode"); }); it("adds a tool and shows it in function calling UI", async () => { const el = renderBuildTab(); + goToStep2(el, "tools"); - // Find add tool form inputs in the right panel + // Find add tool form inputs in the tools panel const allInputs = el.querySelectorAll("input[type='text']") as NodeListOf; - // The first or second input should be the function name + // The first input is the function name const nameInput = allInputs[0]; act(() => setInputValue(nameInput, "search_web")); @@ -115,16 +167,15 @@ describe("BuildTab", () => { it("shows validation error for invalid JSON in tool params", async () => { const el = renderBuildTab(); + goToStep2(el, "tools"); const allInputs = el.querySelectorAll("input[type='text']") as NodeListOf; act(() => setInputValue(allInputs[0], "bad_tool")); // The parameters textarea is in the Add tool form section — it has default valid JSON. - // We need to find the textarea labeled "JSON schema for parameters" in the add form. const paramsTextareas = Array.from(el.querySelectorAll("textarea")).filter( (t) => t.getAttribute("aria-label") === "JSON schema for parameters", ); - // The last one is in the Add tool form (the first may be the message prompt textarea) const paramsTextarea = paramsTextareas[paramsTextareas.length - 1] as HTMLTextAreaElement; act(() => setInputValue(paramsTextarea, "NOT JSON {{{")); @@ -140,6 +191,7 @@ describe("BuildTab", () => { it("enables JSON mode toggle and shows schema editor", async () => { const el = renderBuildTab(); + goToStep2(el, "json"); const toggle = el.querySelector("[role='switch']") as HTMLButtonElement; expect(toggle).not.toBeNull(); @@ -153,6 +205,7 @@ describe("BuildTab", () => { it("shows tool badge in toolbar when tools are added", async () => { const el = renderBuildTab(); + goToStep2(el, "tools"); const allInputs = el.querySelectorAll("input[type='text']") as NodeListOf; act(() => setInputValue(allInputs[0], "my_tool")); @@ -163,7 +216,9 @@ describe("BuildTab", () => { ) as HTMLButtonElement; await act(async () => { addToolBtn.click(); }); - // Badge "1 tool" should appear in toolbar + clickNext(el); // step 2 -> step 3 + + // Badge "1 tool" should appear in the step-3 toolbar expect(el.textContent).toContain("1 tool"); }); @@ -183,6 +238,7 @@ describe("BuildTab", () => { ); const el = renderBuildTab(); + goToStep2(el, "tools"); // Add a tool const allInputs = el.querySelectorAll("input[type='text']") as NodeListOf; @@ -193,14 +249,16 @@ describe("BuildTab", () => { ) as HTMLButtonElement; await act(async () => { addToolBtn.click(); }); - // Type a prompt - const promptTextarea = el.querySelector("textarea[placeholder*='message']") as HTMLTextAreaElement; + clickNext(el); // step 2 -> step 3 + + // Type a prompt (the only textarea left on step 3 is the prompt input) + const promptTextarea = el.querySelector("textarea") as HTMLTextAreaElement; act(() => setInputValue(promptTextarea, "Run this tool")); - // Click Run (label is "runLabel" via mocked t()) + // Click Run (label is "runButton" via mocked t()) const runBtns = el.querySelectorAll("button"); const runBtn = Array.from(runBtns).find( - (b) => b.textContent?.includes("runLabel") && !b.textContent?.includes("clearAll"), + (b) => b.textContent?.includes("runButton"), ) as HTMLButtonElement; await act(async () => { runBtn.click(); }); await act(async () => { @@ -218,8 +276,12 @@ describe("BuildTab", () => { it("shows JSON mode badge in toolbar when JSON mode is enabled", async () => { const el = renderBuildTab(); + goToStep2(el, "json"); const toggle = el.querySelector("[role='switch']") as HTMLButtonElement; await act(async () => { toggle.click(); }); + + clickNext(el); // step 2 -> step 3 + expect(el.textContent).toContain("JSON mode"); }); }); diff --git a/tests/unit/ui/playground-compare-tab.test.tsx b/tests/unit/ui/playground-compare-tab.test.tsx index bbb12ebe53..82fc3ad0e8 100644 --- a/tests/unit/ui/playground-compare-tab.test.tsx +++ b/tests/unit/ui/playground-compare-tab.test.tsx @@ -92,9 +92,9 @@ function renderCompareTab(config = BASE_CONFIG): HTMLDivElement { return el; } -function setInputValue(el: HTMLInputElement, value: string) { +function setInputValue(el: HTMLInputElement | HTMLTextAreaElement, value: string) { const nativeSetter = Object.getOwnPropertyDescriptor( - window.HTMLInputElement.prototype, + el instanceof HTMLTextAreaElement ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype, "value", )?.set; nativeSetter?.call(el, value); @@ -204,6 +204,10 @@ describe("CompareTab", () => { act(() => setInputValue(input, "model-2")); await act(async () => { addBtn.click(); }); + // Run all is disabled until a prompt is entered + const promptTextarea = el.querySelector("[aria-label='User prompt']") as HTMLTextAreaElement; + act(() => setInputValue(promptTextarea, "Compare this")); + const runBtn = el.querySelector("[aria-label='Run all columns']") as HTMLButtonElement; await act(async () => { runBtn.click(); }); @@ -219,6 +223,11 @@ describe("CompareTab", () => { ); const el = renderCompareTab(); + + // Run all is disabled until a prompt is entered + const promptTextarea = el.querySelector("[aria-label='User prompt']") as HTMLTextAreaElement; + act(() => setInputValue(promptTextarea, "Compare this")); + const runBtn = el.querySelector("[aria-label='Run all columns']") as HTMLButtonElement; act(() => { runBtn.click(); }); diff --git a/tests/unit/ui/playground-studio.test.tsx b/tests/unit/ui/playground-studio.test.tsx index 8a975219ba..91db18dbf8 100644 --- a/tests/unit/ui/playground-studio.test.tsx +++ b/tests/unit/ui/playground-studio.test.tsx @@ -68,6 +68,10 @@ vi.mock("@/shared/components/MonacoEditor", () => ({ vi.mock("@/shared/constants/providers", () => ({ ALIAS_TO_ID: {}, + AI_PROVIDERS: {}, + OPENAI_COMPATIBLE_PREFIX: "openai-compatible-", + ANTHROPIC_COMPATIBLE_PREFIX: "anthropic-compatible-", + CLAUDE_CODE_COMPATIBLE_PREFIX: "anthropic-compatible-cc-", })); vi.mock("@/shared/utils/maskEmail", () => ({ diff --git a/tests/unit/ui/provider-plan-config.test.tsx b/tests/unit/ui/provider-plan-config.test.tsx deleted file mode 100644 index 0351a92a54..0000000000 --- a/tests/unit/ui/provider-plan-config.test.tsx +++ /dev/null @@ -1,134 +0,0 @@ -// @vitest-environment jsdom -import React from "react"; -import { act } from "react"; -import { createRoot } from "react-dom/client"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, -})); - -vi.mock("@/shared/components", () => ({ - Button: ({ - children, - onClick, - disabled, - }: { - children: React.ReactNode; - onClick?: () => void; - disabled?: boolean; - }) => ( - - ), -})); - -vi.mock("@/shared/components/ProviderIcon", () => ({ - default: () => , -})); - -vi.mock("@/lib/quota/planRegistry", () => ({ - knownProviders: () => ["openai", "anthropic"], - getKnownPlan: (prov: string) => { - if (prov === "openai") { - return { dimensions: [{ unit: "tokens", window: "daily", limit: 100000 }] }; - } - return null; - }, -})); - -const MOCK_CONNECTIONS = [ - { id: "conn_1", provider: "openai", name: "GPT Account" }, - { id: "conn_2", provider: "anthropic", email: "user@example.com" }, -]; - -const mockFetch = vi.fn(); -vi.stubGlobal("fetch", mockFetch); - -const { default: ProviderPlanConfigClient } = await import( - "../../../src/app/(dashboard)/dashboard/costs/quota-share/plans/ProviderPlanConfigClient" -); - -let container: HTMLDivElement | null = null; -let root: ReturnType | null = null; - -async function renderPage() { - (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = - true; - container = document.createElement("div"); - document.body.appendChild(container); - await act(async () => { - root = createRoot(container!); - root.render(); - }); - // Wait for initial fetch effect to resolve - await act(async () => { - await new Promise((r) => setTimeout(r, 30)); - }); -} - -describe("ProviderPlanConfigClient", { timeout: 15000 }, () => { - beforeEach(() => { - mockFetch.mockImplementation((url: string) => { - if (String(url).includes("/api/providers/client")) { - return Promise.resolve({ - ok: true, - json: () => Promise.resolve({ connections: MOCK_CONNECTIONS }), - } as unknown as Response); - } - if (String(url).includes("/api/quota/plans")) { - return Promise.resolve({ - ok: true, - json: () => Promise.resolve([]), - } as unknown as Response); - } - return Promise.resolve({ - ok: true, - json: () => Promise.resolve({}), - } as unknown as Response); - }); - }); - - afterEach(() => { - if (root && container) act(() => root!.unmount()); - container?.remove(); - container = null; - root = null; - vi.clearAllMocks(); - }); - - it("renders the page title", async () => { - await renderPage(); - expect(document.body.innerHTML).toContain("title"); - }); - - it("renders catalog section with known providers", async () => { - await renderPage(); - // catalogTitle key should appear - expect(document.body.innerHTML).toContain("catalogTitle"); - expect(document.body.innerHTML).toContain("openai"); - }); - - it("renders connection selector with options", async () => { - await renderPage(); - const select = document.querySelector("select") as HTMLSelectElement; - expect(select).not.toBeNull(); - expect(select.options.length).toBeGreaterThan(1); - }); - - it("shows right-panel placeholder when no connection selected", async () => { - await renderPage(); - expect(document.body.innerHTML).toContain("unknownProviderNotice"); - }); - - it("renders save button after selecting a connection", async () => { - await renderPage(); - const select = document.querySelector("select") as HTMLSelectElement; - await act(async () => { - select.value = "conn_1"; - select.dispatchEvent(new Event("change", { bubbles: true })); - }); - expect(document.body.innerHTML).toContain("saveOverrideButton"); - }); -}); diff --git a/tests/unit/ui/same-context-filter.test.tsx b/tests/unit/ui/same-context-filter.test.tsx index ddc9aaef0b..40bcc16b57 100644 --- a/tests/unit/ui/same-context-filter.test.tsx +++ b/tests/unit/ui/same-context-filter.test.tsx @@ -6,7 +6,7 @@ * - RequestRow exports an onSameContext prop * - useTrafficFilters.setSameContext is referenced from TrafficInspectorPageClient */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; @@ -17,21 +17,35 @@ const ROOT = path.resolve( __dirname, "../../../src/app/(dashboard)/dashboard/tools/traffic-inspector" ); +const SRC_ROOT = path.resolve(__dirname, "../../../src"); function read(rel: string): string { return fs.readFileSync(path.join(ROOT, rel), "utf8"); } +function readSrc(rel: string): string { + return fs.readFileSync(path.join(SRC_ROOT, rel), "utf8"); +} + describe("R5-4 same-context filter end-to-end", () => { it("useTrafficStream.applyFilter has sameContextKey branch", () => { - const src = read("hooks/useTrafficStream.ts"); + // The comparison itself now lives in the extracted, independently-testable + // matchesTrafficFilter() helper (src/lib/inspector/matchesTrafficFilter.ts) — + // useTrafficStream.applyFilter just delegates to it. + const hookSrc = read("hooks/useTrafficStream.ts"); assert.ok( - src.includes("sameContextKey") && src.includes("contextKey"), - "applyFilter should branch on sameContextKey / contextKey" + hookSrc.includes("matchesTrafficFilter"), + "applyFilter should delegate to matchesTrafficFilter" + ); + + const matcherSrc = readSrc("lib/inspector/matchesTrafficFilter.ts"); + assert.ok( + matcherSrc.includes("sameContextKey") && matcherSrc.includes("contextKey"), + "matchesTrafficFilter should branch on sameContextKey / contextKey" ); // Must actually exclude requests where contextKey differs assert.ok( - src.includes("req.contextKey !== f.sameContextKey"), + matcherSrc.includes("req.contextKey !== f.sameContextKey"), "should exclude when contextKey !== sameContextKey" ); }); diff --git a/tests/unit/ui/search-tools-compare-tab.test.tsx b/tests/unit/ui/search-tools-compare-tab.test.tsx index 3e014faf8a..3cb92d0ffc 100644 --- a/tests/unit/ui/search-tools-compare-tab.test.tsx +++ b/tests/unit/ui/search-tools-compare-tab.test.tsx @@ -270,12 +270,13 @@ describe("CompareTab", () => { await new Promise((r) => setTimeout(r, 150)); }); - // Check that the table exists and contains overlap info - const table = el.querySelector("table"); - if (table) { + // The results panel renders as a div-based side-by-side layout (not a ) — + // the overlap summary footer lives inside [data-testid='compare-results']. + const resultsPanel = el.querySelector("[data-testid='compare-results']"); + if (resultsPanel) { // URL overlap row should contain a fraction like "1/2" - const tableText = table.textContent ?? ""; - expect(tableText).toMatch(/URL overlap|\d+\/\d+/); + const panelText = resultsPanel.textContent ?? ""; + expect(panelText).toMatch(/in common|\d+\/\d+/); } else { // Loading state is still active — acceptable expect(el.querySelector("[data-testid='compare-loading']")).toBeTruthy(); diff --git a/tests/unit/ui/search-tools-scrape-tab.test.tsx b/tests/unit/ui/search-tools-scrape-tab.test.tsx index 67509d4f47..f63bac664b 100644 --- a/tests/unit/ui/search-tools-scrape-tab.test.tsx +++ b/tests/unit/ui/search-tools-scrape-tab.test.tsx @@ -112,7 +112,9 @@ describe("ScrapeTab", () => { }); const errorEl = el.querySelector("[data-testid='url-error']"); expect(errorEl).toBeTruthy(); - expect(errorEl?.textContent).toContain("URL"); + // next-intl is mocked as a key pass-through above (per repo convention), so the + // rendered text is the raw i18n key, not the translated "URL is required" copy. + expect(errorEl?.textContent).toContain("scrapeUrlRequired"); }); it("shows error for invalid URL", () => { diff --git a/tests/unit/ui/session-recorder-bar.test.tsx b/tests/unit/ui/session-recorder-bar.test.tsx index 2363617277..d4e88c48e3 100644 --- a/tests/unit/ui/session-recorder-bar.test.tsx +++ b/tests/unit/ui/session-recorder-bar.test.tsx @@ -1,7 +1,7 @@ /** * Tests for SessionRecorderBar — start/stop flow + timer logic */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; function formatElapsed(s: number): string { diff --git a/tests/unit/ui/stats-tab.test.tsx b/tests/unit/ui/stats-tab.test.tsx index 3cdd100ee8..03703966e0 100644 --- a/tests/unit/ui/stats-tab.test.tsx +++ b/tests/unit/ui/stats-tab.test.tsx @@ -2,7 +2,7 @@ * Asserts that StatsTab lazy-loads StatsCharts via next/dynamic (ssr: false) * and does NOT statically import anything from "recharts". */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; diff --git a/tests/unit/ui/timing-i18n.test.tsx b/tests/unit/ui/timing-i18n.test.tsx index 5d2ee14a77..e52f840c82 100644 --- a/tests/unit/ui/timing-i18n.test.tsx +++ b/tests/unit/ui/timing-i18n.test.tsx @@ -5,7 +5,7 @@ * Round-3 F-I18N translated ConversationTab/StatsTab/StatsCharts but missed * TimingTab (5 labels) and TimingWaterfall (2 labels). Round-4 closed the gap. */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; diff --git a/tests/unit/ui/traffic-inspector-page.test.tsx b/tests/unit/ui/traffic-inspector-page.test.tsx index f908eab22b..67f1cbd1c0 100644 --- a/tests/unit/ui/traffic-inspector-page.test.tsx +++ b/tests/unit/ui/traffic-inspector-page.test.tsx @@ -1,7 +1,7 @@ /** * Smoke tests for Traffic Inspector page structure and constants */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; describe("Traffic Inspector page smoke tests", () => { diff --git a/tests/unit/ui/use-resizable-panels.test.tsx b/tests/unit/ui/use-resizable-panels.test.tsx index 156d653738..5dee3cdcf1 100644 --- a/tests/unit/ui/use-resizable-panels.test.tsx +++ b/tests/unit/ui/use-resizable-panels.test.tsx @@ -1,7 +1,7 @@ /** * Tests for useResizablePanels — drag changes width, collapse to 48px, localStorage persistence */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; const MIN_WIDTH = 280; diff --git a/tests/unit/ui/use-session-recorder.test.tsx b/tests/unit/ui/use-session-recorder.test.tsx index f4889b244a..f39277a653 100644 --- a/tests/unit/ui/use-session-recorder.test.tsx +++ b/tests/unit/ui/use-session-recorder.test.tsx @@ -4,7 +4,7 @@ * Verifies that during recording, new traffic WS events trigger * POST to /api/tools/traffic-inspector/sessions/{id}/requests. */ -import { describe, it, before, after } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/unit/ui/use-system-proxy-exit-guard.test.tsx b/tests/unit/ui/use-system-proxy-exit-guard.test.tsx index af1eaeeb1b..fa6d7597a5 100644 --- a/tests/unit/ui/use-system-proxy-exit-guard.test.tsx +++ b/tests/unit/ui/use-system-proxy-exit-guard.test.tsx @@ -6,7 +6,7 @@ * This matches how use-traffic-stream.test.tsx tests hook logic (pure logic, * no React renderer needed). */ -import { describe, it, beforeEach } from "node:test"; +import { describe, it, beforeEach } from "vitest"; import assert from "node:assert/strict"; // --------------------------------------------------------------------------- diff --git a/tests/unit/ui/use-traffic-stream.test.tsx b/tests/unit/ui/use-traffic-stream.test.tsx index ce8804fa9b..39ca601e28 100644 --- a/tests/unit/ui/use-traffic-stream.test.tsx +++ b/tests/unit/ui/use-traffic-stream.test.tsx @@ -1,7 +1,7 @@ /** * Tests for useTrafficStream — WebSocket snapshot/new/update/clear + reconnect backoff */ -import { describe, it, before, after, mock } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; diff --git a/tests/unit/ui/use-virtual-list.test.tsx b/tests/unit/ui/use-virtual-list.test.tsx index 5ef5e689fb..bfb1bc9a53 100644 --- a/tests/unit/ui/use-virtual-list.test.tsx +++ b/tests/unit/ui/use-virtual-list.test.tsx @@ -1,7 +1,7 @@ /** * Tests for useVirtualList — virtualizes 1000+ items without rendering all */ -import { describe, it } from "node:test"; +import { describe, it } from "vitest"; import assert from "node:assert/strict"; const ESTIMATED_ROW_HEIGHT = 48; diff --git a/tests/unit/verify-published.test.ts b/tests/unit/verify-published.test.ts new file mode 100644 index 0000000000..b4a9be09df --- /dev/null +++ b/tests/unit/verify-published.test.ts @@ -0,0 +1,40 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + parseVersionArg, + buildDockerArgs, + CONTAINER_SCRIPT, +} from "../../scripts/release/verify-published.mjs"; + +// WS1.4 (v3.8.49 quality plan) — pure-function guards for the post-publish verifier +// (clean-container install of the PUBLISHED bytes + boot). The end-to-end path is +// exercised live against the registry; these pin the safety-relevant logic. + +test("parseVersionArg accepts strict semver incl. prerelease", () => { + assert.equal(parseVersionArg("3.8.48"), "3.8.48"); + assert.equal(parseVersionArg("3.9.0-rc.1"), "3.9.0-rc.1"); +}); + +test("parseVersionArg rejects shell-hostile and malformed input", () => { + for (const bad of ["", "3.8", "latest", "3.8.48; rm -rf /", "$(whoami)", "3.8.48 && x"]) { + assert.equal(parseVersionArg(bad), null, `should reject: ${bad}`); + } +}); + +test("buildDockerArgs passes the version via env, never into the script body", () => { + const args = buildDockerArgs("3.8.48"); + assert.equal(args[0], "run"); + assert.ok(args.includes("VERIFY_VERSION=3.8.48"), "version must travel as -e env"); + const script = args[args.length - 1]; + assert.equal(script, CONTAINER_SCRIPT); + assert.ok(!script.includes("3.8.48"), "script body must not embed the version (Hard Rule #13)"); + assert.ok(args.includes("node:24-slim"), "clean base image"); + assert.ok(args.includes("--rm"), "container must not linger"); +}); + +test("container script installs from the registry and polls health with a version match", () => { + assert.ok(CONTAINER_SCRIPT.includes('npm install -g "omniroute@${VERIFY_VERSION}"')); + assert.ok(CONTAINER_SCRIPT.includes("/api/monitoring/health")); + assert.ok(CONTAINER_SCRIPT.includes("body.version === want"), "must assert the served version"); + assert.ok(CONTAINER_SCRIPT.includes("WRONG VERSION"), "must fail loudly on version mismatch"); +}); diff --git a/tests/unit/web-cookie-validation-proxy-7058.test.ts b/tests/unit/web-cookie-validation-proxy-7058.test.ts new file mode 100644 index 0000000000..9c573020bc --- /dev/null +++ b/tests/unit/web-cookie-validation-proxy-7058.test.ts @@ -0,0 +1,98 @@ +// Regression test for #7058 — zai-web (and every other entry-bearing web-cookie +// provider) never honored a configured HTTP/SOCKS proxy during connection-test / +// cookie validation. +// +// Root cause: validateWebCookieProvider() probed `${baseUrl}/models` via +// directHttpsRequest(), which hardcodes `bypassProxyPatch: true` — forcing +// safeOutboundFetch to use the pre-patch native fetch and skip proxy-context/ +// env-var resolution entirely. That bypass was introduced in #3226 as a narrow, +// documented exception for a single NVIDIA NIM workaround +// (see tests/unit/proxy-bypass-scope-guard-3226.test.ts) but validateWebCookieProvider +// adopted it as its default transport from inception (#4023), silently extending the +// bypass to every web-cookie provider with a registry entry (zai-web among them). +// +// This test proves the cookie-validation probe reaches a local forward proxy +// (via a real CONNECT tunnel — the same mechanism undici uses for both HTTP and +// HTTPS targets) when one is configured via HTTP_PROXY, exactly like the +// specialty web-cookie validators (chatgpt-web, grok-web, ...) already do via +// validationRead/validationWrite. +import test from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import net from "node:net"; + +const { validateWebCookieProvider } = await import("../../src/lib/providers/validation.ts"); +const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); +const { clearDispatcherCache } = await import("../../open-sse/utils/proxyDispatcher.ts"); + +const zaiWebEntry = REGISTRY["zai-web"] as { baseUrl?: string } | undefined; +const ORIGINAL_BASE_URL = zaiWebEntry?.baseUrl; +const ORIGINAL_HTTP_PROXY = process.env.HTTP_PROXY; + +test.after(() => { + if (zaiWebEntry && ORIGINAL_BASE_URL !== undefined) { + zaiWebEntry.baseUrl = ORIGINAL_BASE_URL; + } + if (ORIGINAL_HTTP_PROXY === undefined) { + delete process.env.HTTP_PROXY; + } else { + process.env.HTTP_PROXY = ORIGINAL_HTTP_PROXY; + } + clearDispatcherCache(); +}); + +test("zai-web cookie validation routes through the configured HTTP_PROXY (#7058)", async () => { + assert.ok(zaiWebEntry, "zai-web must have a providerRegistry entry for this test to be meaningful"); + + // Stand-in for chat.z.ai's /models probe target. + const target = http.createServer((_req, res) => { + res.writeHead(200, { "content-type": "application/json" }); + res.end("{}"); + }); + await new Promise((resolve) => target.listen(0, () => resolve())); + const targetPort = (target.address() as net.AddressInfo).port; + + // Minimal forward proxy that only speaks CONNECT (like a real corporate proxy) and + // always tunnels to the local target above, regardless of the requested host — this + // lets the "upstream" host be a non-resolvable placeholder without any real DNS + // dependency, while still proving the request actually reached the proxy. + let sawConnect = false; + const proxy = http.createServer((_req, res) => { + res.writeHead(501); + res.end("CONNECT only"); + }); + proxy.on("connect", (_req, socket) => { + sawConnect = true; + const upstream = net.connect(targetPort, "127.0.0.1", () => { + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + upstream.pipe(socket); + socket.pipe(upstream); + }); + upstream.on("error", () => socket.destroy()); + socket.on("error", () => upstream.destroy()); + }); + await new Promise((resolve) => proxy.listen(0, () => resolve())); + const proxyPort = (proxy.address() as net.AddressInfo).port; + + // A non-local-looking hostname: isLocalAddress()/resolveProxyForRequest() force a + // direct connection for any 127.*/localhost/LAN target, which would defeat this test. + zaiWebEntry!.baseUrl = "http://zai-web-validation-probe-7058.invalid"; + process.env.HTTP_PROXY = `http://127.0.0.1:${proxyPort}`; + clearDispatcherCache(); + + try { + const result = await validateWebCookieProvider({ provider: "zai-web", apiKey: "token=fake" }); + + assert.equal( + sawConnect, + true, + "BUG #7058: zai-web cookie validation never reached the configured HTTP_PROXY " + + "(bypassProxyPatch:true unconditionally uses the native, unpatched fetch)" + ); + assert.equal(result.valid, true, `expected a valid session, got ${JSON.stringify(result)}`); + } finally { + target.close(); + proxy.close(); + clearDispatcherCache(); + } +}); diff --git a/tsconfig.typecheck-dashboard.json b/tsconfig.typecheck-dashboard.json new file mode 100644 index 0000000000..a6cfaddf7c --- /dev/null +++ b/tsconfig.typecheck-dashboard.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "incremental": false + }, + "include": ["src/app/(dashboard)/**/*.ts", "src/app/(dashboard)/**/*.tsx"] +} diff --git a/vitest.config.ts b/vitest.config.ts index 4664608302..6f8d643da5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ test: { environment: "jsdom", globals: true, + setupFiles: ["./tests/_setup/vitestUiPolyfills.ts"], pool: "threads", maxWorkers: 20, fileParallelism: true,