chore(v3.8.4): consolidate pending fixes and roll version back from 3.8.5

Squashes multiple in-flight changes pending release into release/v3.8.4
since the in-progress 3.8.5 has been consolidated back into 3.8.4.

CRITICAL — oauth/codex (multi-account regression revert)
  Revert the proactive expired-flip that #2743 (multi-agent review) added
  to open-sse/executors/base.ts. The new behaviour marked accounts as
  testStatus:"expired" + isActive:false from inside the PROACTIVE refresh
  path whenever isUnrecoverableRefreshError() fired — including transient
  sentinels (refresh_token_reused that the rotation map can recover,
  generic invalid_request blips). On multi-account Codex it sequentially
  disabled working accounts in the DB before any upstream call confirmed
  the failure.

  Keep the classification — that part is legitimate (avoids spreading the
  sentinel into activeCredentials and sending a non-token upstream). Drop
  only the DB mutation: the REACTIVE path in chatCore.ts:~3912 still
  flips the account to expired after the upstream confirms the auth
  failure, which is the correct moment (by then the rotation map at
  tokenRefresh.ts:~1541 and the DB-staleness check have already had
  their chance to recover). Marked the block "SOURCE OF TRUTH — do not
  flip the proactive path back. Ask the operator first." with the
  regression history (ad3d4b696 -> 0c94c397d -> this revert) so a future
  review does not re-introduce the regression on autopilot.

oauth/kiro — centralize social-flow constants in KIRO_CONFIG
  social-authorize/route.ts and social-exchange/route.ts duplicated the
  AWS Kiro device-auth URL and the "kiro-cli" public client identifier.
  Move both to KIRO_CONFIG (alongside the existing AWS SSO OIDC + social
  auth fields) and add an env override on socialClientId so operators
  can pin a custom value via KIRO_OAUTH_CLIENT_ID. New KIRO_CONFIG
  fields: socialClientId (env-overridable), socialDeviceAuthorizeUrl,
  socialDevicePollUrl. tests/unit/oauth-kiro.test.ts locks the contract:
  routes must import KIRO_CONFIG and must not inline the AWS URL or
  "kiro-cli" literal.

dashboard/providers — memoize ProviderCard lookup constants
  Move KIND_LABEL and DOT_COLORS into useMemo so they don't recreate on
  every render. Functional parity, slightly cheaper re-renders.

test(authz) — lockdown Next.js 16 proxy.ts contract
  New tests/unit/authz/proxy-contract.test.ts asserts the file lives at
  src/proxy.ts (not src/middleware.ts), exports the proxy function,
  delegates to runAuthzPipeline with enforce:true, and the matcher
  covers every prefix mounted under /api so unauthenticated requests
  cannot bypass the centralized tier checks.

version — roll back from 3.8.5 to 3.8.4
  CHANGELOG.md consolidates the unreleased 3.8.5 entries into the
  3.8.4 section. Mirror that in package.json, package-lock.json and
  docs/reference/openapi.yaml. .source/* picked up the regenerated
  fumadocs section ordering.

docs — env contract additions
  Add KIRO_OAUTH_CLIENT_ID and OMNIROUTE_PROXY_FETCH_DEBUG to
  .env.example and docs/reference/ENVIRONMENT.md so the env-doc-sync
  check stays green.
This commit is contained in:
diegosouzapw 2026-05-26 16:56:13 -03:00
parent bcc2427f62
commit 448b65af2c
15 changed files with 350 additions and 128 deletions

View file

@ -132,6 +132,11 @@ OMNIROUTE_USE_TURBOPACK=1
# Used by: src/lib/credentialHealth/scheduler.ts
# OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK=false
# Set to "true" to emit `[ProxyFetch]` debug logs from the Vercel relay path
# in open-sse/utils/proxyFetch.ts. Off by default to avoid leaking routing
# hints in production logs.
# OMNIROUTE_PROXY_FETCH_DEBUG=true
# Docker production port mappings (docker-compose.prod.yml only).
# These set the HOST-side published ports. Container ports use PORT/API_PORT.
# PROD_DASHBOARD_PORT=20130
@ -666,6 +671,11 @@ CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
# Optional override for the Kiro social device-code OAuth clientId. Kiro's
# device endpoint accepts any non-empty string and behaves like a User-Agent
# rather than a secret. Only override if AWS ever starts enforcing this field.
# Used by: src/lib/oauth/constants/oauth.ts (KIRO_CONFIG.socialClientId).
# KIRO_OAUTH_CLIENT_ID=kiro-cli
QODER_USER_AGENT="Qoder-Cli"
QWEN_USER_AGENT="QwenCode/0.15.11 (linux; x64)"
CURSOR_USER_AGENT="Cursor/3.4"

File diff suppressed because one or more lines are too long

View file

@ -4,9 +4,9 @@ import { default as __fd_glob_63 } from "../docs/routing/meta.json?collection=do
import { default as __fd_glob_62 } from "../docs/reference/openapi.yaml?collection=docs"
import { default as __fd_glob_61 } from "../docs/reference/meta.json?collection=docs"
import { default as __fd_glob_60 } from "../docs/ops/meta.json?collection=docs"
import { default as __fd_glob_59 } from "../docs/guides/meta.json?collection=docs"
import { default as __fd_glob_58 } from "../docs/frameworks/meta.json?collection=docs"
import { default as __fd_glob_57 } from "../docs/compression/meta.json?collection=docs"
import { default as __fd_glob_59 } from "../docs/compression/meta.json?collection=docs"
import { default as __fd_glob_58 } from "../docs/guides/meta.json?collection=docs"
import { default as __fd_glob_57 } from "../docs/frameworks/meta.json?collection=docs"
import { default as __fd_glob_56 } from "../docs/architecture/meta.json?collection=docs"
import { default as __fd_glob_55 } from "../docs/meta.json?collection=docs"
import * as __fd_glob_54 from "../docs/security/STEALTH_GUIDE.md?collection=docs"
@ -32,28 +32,28 @@ import * as __fd_glob_35 from "../docs/ops/PROXY_GUIDE.md?collection=docs"
import * as __fd_glob_34 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"
import * as __fd_glob_33 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"
import * as __fd_glob_32 from "../docs/ops/COVERAGE_PLAN.md?collection=docs"
import * as __fd_glob_31 from "../docs/guides/USER_GUIDE.md?collection=docs"
import * as __fd_glob_30 from "../docs/guides/UNINSTALL.md?collection=docs"
import * as __fd_glob_29 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
import * as __fd_glob_28 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
import * as __fd_glob_27 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
import * as __fd_glob_26 from "../docs/guides/PWA_GUIDE.md?collection=docs"
import * as __fd_glob_25 from "../docs/guides/KIRO_SETUP.md?collection=docs"
import * as __fd_glob_24 from "../docs/guides/I18N.md?collection=docs"
import * as __fd_glob_23 from "../docs/guides/FEATURES.md?collection=docs"
import * as __fd_glob_22 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
import * as __fd_glob_21 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
import * as __fd_glob_20 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
import * as __fd_glob_19 from "../docs/frameworks/SKILLS.md?collection=docs"
import * as __fd_glob_18 from "../docs/frameworks/OPENCODE.md?collection=docs"
import * as __fd_glob_17 from "../docs/frameworks/MEMORY.md?collection=docs"
import * as __fd_glob_16 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
import * as __fd_glob_15 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
import * as __fd_glob_14 from "../docs/frameworks/EVALS.md?collection=docs"
import * as __fd_glob_13 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
import * as __fd_glob_12 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
import * as __fd_glob_11 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
import * as __fd_glob_10 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
import * as __fd_glob_31 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
import * as __fd_glob_30 from "../docs/frameworks/SKILLS.md?collection=docs"
import * as __fd_glob_29 from "../docs/frameworks/OPENCODE.md?collection=docs"
import * as __fd_glob_28 from "../docs/frameworks/MEMORY.md?collection=docs"
import * as __fd_glob_27 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
import * as __fd_glob_26 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
import * as __fd_glob_25 from "../docs/frameworks/EVALS.md?collection=docs"
import * as __fd_glob_24 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
import * as __fd_glob_23 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
import * as __fd_glob_22 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
import * as __fd_glob_21 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
import * as __fd_glob_20 from "../docs/guides/USER_GUIDE.md?collection=docs"
import * as __fd_glob_19 from "../docs/guides/UNINSTALL.md?collection=docs"
import * as __fd_glob_18 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
import * as __fd_glob_17 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
import * as __fd_glob_16 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
import * as __fd_glob_15 from "../docs/guides/PWA_GUIDE.md?collection=docs"
import * as __fd_glob_14 from "../docs/guides/KIRO_SETUP.md?collection=docs"
import * as __fd_glob_13 from "../docs/guides/I18N.md?collection=docs"
import * as __fd_glob_12 from "../docs/guides/FEATURES.md?collection=docs"
import * as __fd_glob_11 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
import * as __fd_glob_10 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
import * as __fd_glob_9 from "../docs/compression/RTK_COMPRESSION.md?collection=docs"
import * as __fd_glob_8 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"
import * as __fd_glob_7 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"
@ -72,4 +72,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
}
}>({"doc":{"passthroughs":["extractedReferences"]}});
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "compression/meta.json": __fd_glob_57, "frameworks/meta.json": __fd_glob_58, "guides/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "frameworks/A2A-SERVER.md": __fd_glob_10, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_11, "frameworks/CLOUD_AGENT.md": __fd_glob_12, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_13, "frameworks/EVALS.md": __fd_glob_14, "frameworks/GAMIFICATION.md": __fd_glob_15, "frameworks/MCP-SERVER.md": __fd_glob_16, "frameworks/MEMORY.md": __fd_glob_17, "frameworks/OPENCODE.md": __fd_glob_18, "frameworks/SKILLS.md": __fd_glob_19, "frameworks/WEBHOOKS.md": __fd_glob_20, "guides/DOCKER_GUIDE.md": __fd_glob_21, "guides/ELECTRON_GUIDE.md": __fd_glob_22, "guides/FEATURES.md": __fd_glob_23, "guides/I18N.md": __fd_glob_24, "guides/KIRO_SETUP.md": __fd_glob_25, "guides/PWA_GUIDE.md": __fd_glob_26, "guides/SETUP_GUIDE.md": __fd_glob_27, "guides/TERMUX_GUIDE.md": __fd_glob_28, "guides/TROUBLESHOOTING.md": __fd_glob_29, "guides/UNINSTALL.md": __fd_glob_30, "guides/USER_GUIDE.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, });
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "frameworks/meta.json": __fd_glob_57, "guides/meta.json": __fd_glob_58, "compression/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "guides/DOCKER_GUIDE.md": __fd_glob_10, "guides/ELECTRON_GUIDE.md": __fd_glob_11, "guides/FEATURES.md": __fd_glob_12, "guides/I18N.md": __fd_glob_13, "guides/KIRO_SETUP.md": __fd_glob_14, "guides/PWA_GUIDE.md": __fd_glob_15, "guides/SETUP_GUIDE.md": __fd_glob_16, "guides/TERMUX_GUIDE.md": __fd_glob_17, "guides/TROUBLESHOOTING.md": __fd_glob_18, "guides/UNINSTALL.md": __fd_glob_19, "guides/USER_GUIDE.md": __fd_glob_20, "frameworks/A2A-SERVER.md": __fd_glob_21, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_22, "frameworks/CLOUD_AGENT.md": __fd_glob_23, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_24, "frameworks/EVALS.md": __fd_glob_25, "frameworks/GAMIFICATION.md": __fd_glob_26, "frameworks/MCP-SERVER.md": __fd_glob_27, "frameworks/MEMORY.md": __fd_glob_28, "frameworks/OPENCODE.md": __fd_glob_29, "frameworks/SKILLS.md": __fd_glob_30, "frameworks/WEBHOOKS.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, });

View file

@ -2,54 +2,12 @@
## [Unreleased]
## [3.8.5] — 2026-05-26
## [3.8.4] — 2026-05-26
### 🔒 Security
- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated — Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712)
- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749)
### 🔧 Bug Fixes
- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 — extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650)
- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724)
- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker — they were invisible because they never get rows in `provider_connections` (#2737)
- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695)
- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658)
- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 → cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208)
- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689)
- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460)
- **mcp:** break circular await deadlock in compliance→callLogs + Kiro refresh resilience (#2747)
- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744)
- **deepseek-web:** lazy start session refresh (#2742)
- **docker:** keep fumadocs doc assets in Docker build context (#2741)
- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740)
- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736)
### 🏆 Hall de Contribuidores
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
@diegosouzapw, @disonjer, @herjarsa, @janeza2, @oyi77, @thanet-s
## [Unreleased]
### ✨ New Features
- **proxy:** serverless relay endpoints with rate limiting (#2734)
- **pwa:** enhanced manifest + push notification support (#2733)
- **auth:** API key groups with model-level permissions (#2732)
- **playground:** combo routing visual simulator (#2731)
- **resilience:** credential health check + adaptive circuit breaker (#2730)
### ♻️ Refactors
- **openapi:** api endpoints audit (#2729)
### 🔧 Bug Fixes
- **db:** hotfix migration version collision (068_services + 068_webhooks_kind_metadata) (#2727)
## [3.8.4] — 2026-05-25
- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated — Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712 — thanks @diegosouzapw)
- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749 — thanks @diegosouzapw)
### ✨ New Features
@ -71,6 +29,11 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
- **feat(sla):** SLA dashboard with uptime/latency/error rate queries, summary/trend APIs, uptime badges and sparklines
- **feat(routing-analytics):** AI-powered usage pattern analysis and routing recommendations — combo_metrics queries, hourly failure heatmap, provider breakdown, cost-vs-latency scatter chart
- **feat(teams):** fixed team execution with 13 git worktrees and project-level team configs
- **feat(providers):** add Inner.ai provider support with native executor, translation support, and model catalog definitions (#2704 — thanks @df4p)
- **feat(proxy):** unified free proxy pool, Vercel Relay serverless endpoints, and a redesigned 4-tab proxy dashboard interface (#2705 — thanks @diegosouzapw)
- **feat(webhooks):** 3-step configuration wizard for Slack, Telegram, Discord, and Custom webhook destinations, with reorganized React components (#2703 — thanks @diegosouzapw)
- **feat(openapi):** comprehensive API endpoints content audit with 100% schema coverage, authz security tiers, and full i18n localization support (#2701 — thanks @diegosouzapw)
- **feat(providers):** add BluesMinds, FreeModel.dev, and FreeAIAPIKey to the provider catalog (#2709 — thanks @oyi77)
- **chore(deps):** added ws + @types/ws for WebSocket support, recharts ^3.8.1 for analytics charts
### 🔧 Bug Fixes
@ -78,6 +41,30 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
- Fix combo cascade skipping on credential check timeout
- Fix team sessions going idle (worktree initialization)
- **feat(providers):** enhance Google Gemini, CLI, and Antigravity resilience and features — introduces explicit TypeScript typing to translation layers, adds new Gemini 2.0 models, implements backoff and retry logic in the Gemini CLI executor, extracts Google Search grounding metadata into standard `citations`, and adds backend definitions for the `vertex-partner` provider. ([#2676](https://github.com/diegosouzapw/OmniRoute/pull/2676) — thanks @alltomatos)
- **fix(proxy):** atomically save and assign custom dashboard proxies in a single SQLite transaction, preventing orphan configuration rows (#2697 — thanks @terence71-glitch)
- **fix(reasoning):** inject thinking blocks into Claude-format messages for Kimi K2 to prevent infinite tool-calling loops (#2699 — thanks @herjarsa)
- **fix(antigravity):** default exhausted quota status display to 0% instead of 100% (#2700 — thanks @ahmet-cetinkaya)
- **fix(electron):** add Caps Lock indicator, custom reset warnings, and suppress shell window spawning on startup (#2714 — thanks @benzntech)
- **fix(combos):** resolve context handoff tags ordering issue and enforce a 60-second request timeout limit per combo target to prevent capacity leaks (#2717 — thanks @herjarsa)
- **fix(oauth):** resolve parallel token refresh race conditions in Codex and implement comprehensive error checking across OAuth providers (#2718 — thanks @diegosouzapw)
- **fix(docker):** install `python3`, `make`, and `g++` in the Docker builder stage to support native Node.js addon compilation (#2713 — thanks @mrmm)
- **fix(i18n):** restore real hint and placeholder translation strings for web-cookie providers in `en.json` (#2694 — thanks @diegosouzapw)
- **fix(db):** resolve migration version prefix collision between services and webhook metadata tables (#2727 — thanks @diegosouzapw)
- **fix(vision-bridge):** ensure images are processed when a vision-capable model is matched through a combo routing mapping (#2706 — thanks @herjarsa)
- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 — extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650 — thanks @disonjer)
- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724 — thanks @thanet-s)
- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker — they were invisible because they never get rows in `provider_connections` (#2737 — thanks @herjarsa)
- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695 — thanks @diegosouzapw)
- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658 — thanks @diegosouzapw)
- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 → cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208 — thanks @diegosouzapw)
- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689 — thanks @disonjer)
- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460 — thanks @benzntech)
- **mcp:** break circular await deadlock in compliance→callLogs + Kiro refresh resilience (#2747 — thanks @disonjer)
- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744 — thanks @oyi77)
- **deepseek-web:** lazy start session refresh (#2742 — thanks @thanet-s)
- **docker:** keep fumadocs doc assets in Docker build context (#2741 — thanks @janeza2)
- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740 — thanks @herjarsa)
- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736 — thanks @herjarsa)
### 🚀 Embedded Services
@ -95,6 +82,11 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
- **DB migration 071** (originally 068, renumbered post-merge to avoid collision with `068_free_proxies` and `068_webhooks_kind_metadata`) — extends `version_manager` table with `autoStart`, `autoUpdate`, `providerExpose`, `apiKey`, and `port` columns. `migrationRunner.ts` now throws at boot if two `.sql` files share the same numeric prefix.
- All service routes classified as `LOCAL_ONLY` in `routeGuard.ts`; loopback enforcement is unconditional before any auth check (leaked JWT via tunnel cannot trigger process spawning).
### 🏆 Hall de Contribuidores
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
@ahmet-cetinkaya, @alltomatos, @benzntech, @Chewji9875, @df4p, @diegosouzapw, @disonjer, @herjarsa, @janeza2, @mrmm, @oyi77, @thanet-s, @terence71-glitch
---
## [3.8.3] — 2026-05-24

View file

@ -93,6 +93,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `OMNIROUTE_MIGRATIONS_DIR` | _(auto-detect)_ | `src/lib/db/migrationRunner.ts` | Override the directory that the migration runner scans. Useful when shipping bundled migrations in custom builds. |
| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. |
| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. |
| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Set to `"true"` to emit `[ProxyFetch]` debug logs on the Vercel relay path. Off by default to avoid leaking routing hints. |
| `BATCH_RETRY_DURATION_MS` | `86400000` (24h) | `open-sse/services/batchProcessor.ts` | Maximum retry window for individual batch items (ms). Items exceeding this duration are marked failed. |
| `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | Base delay (ms) for exponential backoff on batch item retries. |
| `BATCH_BACKOFF_MAX_MS` | `3600000` (1h) | `open-sse/services/batchProcessor.ts` | Cap (ms) for exponential backoff between batch item retries. |
@ -430,6 +431,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | When Antigravity IDE updates |
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates |
| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | Override the Kiro social device-code `clientId` (public id) |
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
| `QWEN_USER_AGENT` | `QwenCode/0.15.9 (linux; x64)` | When Qwen Code updates |
| `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates |

View file

@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.8.5
version: 3.8.4
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,

View file

@ -590,25 +590,47 @@ export class BaseExecutor {
);
if (refreshed && !proactivePersistRan) {
// Classify unrecoverable sentinels before spreading. Without this guard
// an { error: "unrecoverable_refresh_error", code } object passes the
// truthy check and pollutes activeCredentials with garbage, causing the
// executor to send a non-token to upstream. Mirrors the reactive path
// in chatCore.ts and keeps the proactive path's behaviour consistent.
// ─────────────────────────────────────────────────────────────────────
// ⚠️ SOURCE OF TRUTH — do not flip the proactive path back to
// "persist expired+inactive". Ask the operator first.
//
// History (do not repeat past regressions):
// - ad3d4b696 (#2718, 2026-05-25): per-connection mutex + onPersist
// wiring so multi-account Codex (rotating refresh tokens) stops
// hitting refresh_token_reused under concurrent load.
// - 0c94c397d (#2743, 2026-05-26): a multi-agent review added a
// `await onCredentialsRefreshed({ testStatus: "expired",
// isActive: false })` here. That BROKE multi-account Codex —
// transient sentinels (refresh_token_reused recoverable via
// rotation map; generic invalid_request blips) were treated as
// terminal, so the proactive path sequentially disabled
// working accounts in the DB before any upstream call confirmed
// the failure. Reverted intentionally.
//
// Contract for the PROACTIVE refresh path:
// - Classify the sentinel ONLY to avoid spreading it into
// activeCredentials (which would send a non-token upstream).
// - DO NOT persist `{ testStatus: "expired", isActive: false }`
// from here. That decision belongs to the REACTIVE path in
// open-sse/handlers/chatCore.ts:~3912, which runs AFTER the
// upstream confirmed the auth failure. By then the rotation
// map (tokenRefresh.ts:~1541) and the DB-staleness check have
// already had their chance to recover the request.
//
// If a future review/agent thinks the expired-flip is "missing"
// here, STOP — flipping it here re-introduces the multi-account
// Codex regression. Discuss with the operator before touching.
// ─────────────────────────────────────────────────────────────────────
if (isUnrecoverableRefreshError(refreshed)) {
const refreshCode = (refreshed as Record<string, unknown>).code;
log?.error?.(
log?.warn?.(
"TOKEN",
`${this.provider.toUpperCase()} | unrecoverable refresh — marking account expired (code=${String(refreshCode ?? "unknown")})`
`${this.provider.toUpperCase()} | proactive refresh returned unrecoverable sentinel (code=${String(refreshCode ?? "unknown")}); keeping stale credentials, deferring to reactive path.`
);
if (onCredentialsRefreshed) {
await onCredentialsRefreshed({
testStatus: "expired",
isActive: false,
});
}
// Don't spread the sentinel — keep stale credentials so the next
// upstream call surfaces the real auth error to the client.
// Intentionally NOT spreading the sentinel and NOT persisting
// expired status. The next upstream call either succeeds (rotation
// map / DB-staleness saved us) or fails — chatCore.ts then marks
// the account expired with confidence.
} else {
activeCredentials = {
...credentials,

25
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.8.5",
"version": "3.8.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.8.5",
"version": "3.8.4",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@ -90,6 +90,7 @@
"@testing-library/react": "^16.3.2",
"@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "latest",
"@types/keytar": "^4.4.2",
"@types/node": "^25.9.1",
"@types/react": "^19.2.15",
@ -5277,6 +5278,16 @@
"@types/node": "*"
}
},
"node_modules/@types/bun": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz",
"integrity": "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.3.14"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@ -7360,6 +7371,16 @@
"node": ">=8.0.0"
}
},
"node_modules/bun-types": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.14.tgz",
"integrity": "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.8.5",
"version": "3.8.4",
"description": "Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"type": "module",
"bin": {
@ -259,7 +259,12 @@
"postcss": "^8.5.14",
"ip-address": "10.2.0",
"qs": "^6.15.2",
"uuid": "^14.0.0"
"uuid": "^14.0.0",
"cli-table3": {
"ansi-regex": "^5.0.1",
"strip-ansi": "^6.0.1",
"string-width": "^4.2.3"
}
},
"private": true
}

View file

@ -1,7 +1,7 @@
"use client";
import type { MouseEvent, ReactNode } from "react";
import { useState } from "react";
import { useState, useMemo } from "react";
import Image from "next/image";
import Link from "next/link";
import { useTranslations } from "next-intl";
@ -29,19 +29,6 @@ interface ProviderStats {
codexFastActive?: boolean;
}
const KIND_LABEL: Record<string, string> = {
llm: "Chat",
embedding: "Embed",
image: "Image",
imageToText: "I→T",
tts: "TTS",
stt: "STT",
webSearch: "Search",
webFetch: "Fetch",
video: "Video",
music: "Music",
};
interface ProviderCardProps {
providerId: string;
provider: {
@ -62,20 +49,6 @@ interface ProviderCardProps {
onToggle: (active: boolean) => void;
}
const DOT_COLORS: Record<string, string> = {
free: "bg-green-500",
"no-auth": "bg-stone-500",
oauth: "bg-blue-500",
apikey: "bg-amber-500",
compatible: "bg-orange-500",
"web-cookie": "bg-purple-500",
search: "bg-teal-500",
audio: "bg-rose-500",
local: "bg-emerald-500",
"upstream-proxy": "bg-indigo-500",
"cloud-agent": "bg-violet-500",
};
function getStatusDisplay(
connected: number,
error: number,
@ -128,6 +101,39 @@ export default function ProviderCard({
const tp = useTranslations("miniPlayground");
const [testExpanded, setTestExpanded] = useState<boolean>(false);
const KIND_LABEL = useMemo<Record<string, string>>(
() => ({
llm: "Chat",
embedding: "Embed",
image: "Image",
imageToText: "I→T",
tts: "TTS",
stt: "STT",
webSearch: "Search",
webFetch: "Fetch",
video: "Video",
music: "Music",
}),
[]
);
const DOT_COLORS = useMemo<Record<string, string>>(
() => ({
free: "bg-green-500",
"no-auth": "bg-stone-500",
oauth: "bg-blue-500",
apikey: "bg-amber-500",
compatible: "bg-orange-500",
"web-cookie": "bg-purple-500",
search: "bg-teal-500",
audio: "bg-rose-500",
local: "bg-emerald-500",
"upstream-proxy": "bg-indigo-500",
"cloud-agent": "bg-violet-500",
}),
[]
);
// Show the Test button for LLM providers (when serviceKinds includes "llm"
// OR when the provider has no explicit serviceKinds but is a regular LLM provider
// i.e. not a search/audio/cloud-agent type).

View file

@ -1,7 +1,6 @@
import { NextResponse } from "next/server";
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev";
import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth";
/**
* GET /api/oauth/kiro/social-authorize
@ -26,11 +25,11 @@ export async function GET(request) {
const loginProvider = provider === "google" ? "Google" : "Github";
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/authorization`, {
const response = await fetch(KIRO_CONFIG.socialDeviceAuthorizeUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: "kiro-cli",
clientId: KIRO_CONFIG.socialClientId,
loginProvider,
}),
});

View file

@ -6,8 +6,7 @@ import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/lib/cloudSync";
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev";
import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth";
const socialExchangeSchema = z.object({
deviceCode: z.string().min(1, "Missing deviceCode or provider"),
@ -50,10 +49,10 @@ export async function POST(request: Request) {
try {
const { deviceCode, provider } = validation.data;
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/poll`, {
const response = await fetch(KIRO_CONFIG.socialDevicePollUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceCode, clientId: "kiro-cli" }),
body: JSON.stringify({ deviceCode, clientId: KIRO_CONFIG.socialClientId }),
});
const data = await response.json();

View file

@ -258,6 +258,15 @@ export const KIRO_CONFIG = {
socialLoginUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/login",
socialTokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token",
socialRefreshUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
// Social device-code flow (Google/GitHub).
// `socialClientId` is a public CLI identifier — Kiro's device endpoint accepts
// any non-empty string and behaves like a User-Agent rather than a secret.
// The env override exists so operators on locked-down builds can pin a
// custom value if AWS ever starts enforcing this field (Hard Rule #11 spirit).
socialClientId: process.env.KIRO_OAUTH_CLIENT_ID || "kiro-cli",
socialDeviceAuthorizeUrl:
"https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/authorization",
socialDevicePollUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/poll",
// Auth methods
authMethods: ["builder-id", "idc", "google", "github", "import"],
};

View file

@ -0,0 +1,81 @@
/**
* Next.js 16 Proxy File Contract Lockdown Test
*
* Next.js 16 deprecated `middleware.ts` in favour of `proxy.ts` (commit
* 3fb72b973 renamed our copy to match the new convention). The framework
* only invokes this file when ALL of the following hold:
*
* 1. The file lives at `src/proxy.ts` (since `src/app` is the app dir).
* 2. It exports a function named exactly `proxy` (or default).
* 3. The function delegates to `runAuthzPipeline` with `enforce: true`.
* 4. The `config.matcher` covers every prefix routes are mounted under,
* so unauthenticated requests cannot slip past the centralized
* authorization tiers (PUBLIC / CLIENT_API / MANAGEMENT).
*
* Without ANY of these guarantees the pipeline silently becomes dead code
* and every `/api/*` route falls back to per-route self-enforcement, which
* is the failure mode that led to the v3.8.4 hardening pass. Lock the
* contract down here so a future rename / refactor cannot regress it
* unnoticed.
*
* @see docs/security/ROUTE_GUARD_TIERS.md
* @see https://nextjs.org/docs/app/api-reference/file-conventions/proxy
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
test("Next.js 16 proxy file exists at src/proxy.ts (not src/middleware.ts)", () => {
assert.ok(fs.existsSync("src/proxy.ts"), "src/proxy.ts must exist (Next.js 16 file convention)");
assert.ok(
!fs.existsSync("src/middleware.ts"),
"src/middleware.ts must NOT exist — Next.js 16 deprecated middleware.ts, the active file is src/proxy.ts"
);
});
test("proxy.ts exports a function named 'proxy' (Next.js 16 requires this exact name)", () => {
const content = fs.readFileSync("src/proxy.ts", "utf8");
assert.match(
content,
/export\s+async\s+function\s+proxy\s*\(/,
"must export `async function proxy(...)` — Next.js 16 only invokes this exact name"
);
});
test("proxy.ts delegates to runAuthzPipeline with enforce: true", () => {
const content = fs.readFileSync("src/proxy.ts", "utf8");
assert.match(
content,
/runAuthzPipeline\([^)]*\{\s*enforce:\s*true\s*\}\s*\)/,
"must call runAuthzPipeline with { enforce: true } — otherwise the pipeline runs in observe-only mode and never blocks"
);
});
test("proxy.ts config.matcher covers every /api/* route plus dashboard and v1 aliases", () => {
const content = fs.readFileSync("src/proxy.ts", "utf8");
// Required prefixes — drop one and the corresponding routes go unguarded.
const requiredMatchers = [
'"/api/:path*"',
'"/dashboard/:path*"',
'"/v1/:path*"',
'"/chat/:path*"',
'"/responses/:path*"',
'"/codex/:path*"',
'"/models"',
];
for (const matcher of requiredMatchers) {
assert.ok(
content.includes(matcher),
`proxy.ts config.matcher must include ${matcher} — otherwise routes under that prefix bypass the authz pipeline`
);
}
});
test("proxy.ts does not declare runtime: 'edge' (Next.js 16 proxy is Node-only)", () => {
const content = fs.readFileSync("src/proxy.ts", "utf8");
assert.ok(
!/runtime:\s*['"]edge['"]/.test(content),
"proxy.ts MUST NOT set runtime: 'edge' — Next.js 16 only supports nodejs in proxy.ts. The pipeline depends on Node-only modules (jose, better-sqlite3) and would crash at request time on the edge runtime."
);
});

View file

@ -0,0 +1,76 @@
/**
* Kiro IDE OAuth Configuration Lockdown Tests
*
* Ensures the Kiro device-code social-login flow reads its CLI identifier and
* AWS auth-service URLs from `KIRO_CONFIG` instead of inline literals. The
* strings (`"kiro-cli"` and `https://prod.us-east-1.auth.desktop.kiro.dev/...`)
* used to live duplicated across `social-authorize/route.ts` and
* `social-exchange/route.ts`. Centralising them avoids drift if AWS ever
* publishes a per-customer client id and matches the
* `CLAUDE_CONFIG`/`CODEX_CONFIG` pattern that allows operators to override
* via `process.env.KIRO_OAUTH_CLIENT_ID` (Hard Rule #11 spirit operators
* can pin a custom id without patching source).
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import { KIRO_CONFIG } from "../../src/lib/oauth/constants/oauth";
test("KIRO_CONFIG exposes the social-flow client id and device-code URLs", () => {
assert.equal(typeof KIRO_CONFIG.socialClientId, "string");
assert.ok(KIRO_CONFIG.socialClientId.length > 0, "socialClientId must be non-empty");
assert.ok(
KIRO_CONFIG.socialDeviceAuthorizeUrl.startsWith("https://"),
"socialDeviceAuthorizeUrl must use HTTPS"
);
assert.ok(
KIRO_CONFIG.socialDevicePollUrl.startsWith("https://"),
"socialDevicePollUrl must use HTTPS"
);
assert.ok(
KIRO_CONFIG.socialDeviceAuthorizeUrl.endsWith("/oauth/device/authorization"),
"socialDeviceAuthorizeUrl must point at the AWS Kiro device authorize endpoint"
);
assert.ok(
KIRO_CONFIG.socialDevicePollUrl.endsWith("/oauth/device/poll"),
"socialDevicePollUrl must point at the AWS Kiro device poll endpoint"
);
});
test("KIRO_CONFIG.socialClientId default matches the public CLI identifier 'kiro-cli'", () => {
// We only assert the default when no env override is set, so CI can pin a
// custom id via KIRO_OAUTH_CLIENT_ID without breaking this test.
if (!process.env.KIRO_OAUTH_CLIENT_ID) {
assert.equal(KIRO_CONFIG.socialClientId, "kiro-cli");
}
});
test("Kiro social-flow routes do not duplicate the AWS auth URL or 'kiro-cli' literal", () => {
// Catches future refactors that copy a hard-coded URL/identifier back into
// either route. The string "kiro-cli" may still appear as an env-var name
// in comments, so we grep for the specific *literal* shapes we replaced.
const routePaths = [
"src/app/api/oauth/kiro/social-authorize/route.ts",
"src/app/api/oauth/kiro/social-exchange/route.ts",
];
for (const routePath of routePaths) {
const content = fs.readFileSync(routePath, "utf8");
assert.ok(
content.includes('from "@/lib/oauth/constants/oauth"'),
`${routePath} must import KIRO_CONFIG from @/lib/oauth/constants/oauth`
);
assert.ok(
content.includes("KIRO_CONFIG.socialClientId"),
`${routePath} must reference KIRO_CONFIG.socialClientId instead of the "kiro-cli" literal`
);
assert.ok(
!/['"]kiro-cli['"]/.test(content),
`${routePath} must NOT inline the "kiro-cli" literal — use KIRO_CONFIG.socialClientId`
);
assert.ok(
!/['"]https:\/\/prod\.us-east-1\.auth\.desktop\.kiro\.dev/.test(content),
`${routePath} must NOT inline the Kiro auth service URL — use KIRO_CONFIG.socialDeviceAuthorizeUrl / socialDevicePollUrl`
);
}
});