fix(tests): drain base-red cluster from 2026-08-23 merges (#9985) (#11280)

Validated on a 2-PR combined board: 158/158 across the drain's 17 focused test files (incl. integration qdrant-routes), typecheck:core + dashboard-typecheck clean, env-doc-sync, mutation-test-coverage, cli-i18n, file-size, changelog-integrity all green. Every red discriminated per-item as stale-test (contract intentionally moved, citing the origin PR) or real bug (fixed, never masked) across 3 base-advance waves (#9985): stryker registration, i18n gaps (health.* namespace mismatch + pt-BR parity), combo-token-window pins (872K), CLI catalog counts (prime-agent), the isComboStep single-target reasoning-transport-fallback regression, container-guard hermeticity, and 26 unsuppressed ESLint errors from the wave (typed qdrant adapter, import trim, justified disable with precedent). Drains the accumulated base-red cluster.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-23 20:32:46 -03:00
committed by GitHub
parent 9cb91dee74
commit 67fba53190
28 changed files with 251 additions and 118 deletions

View File

@@ -65,6 +65,12 @@ INITIAL_PASSWORD=CHANGEME
# OMNIROUTE_RELEASE_REF=origin/main # OMNIROUTE_RELEASE_REF=origin/main
# OMNIROUTE_ALLOW_CANARY_BUILD=1 # OMNIROUTE_ALLOW_CANARY_BUILD=1
# Build-phase signal (#10060). Set to 1 by scripts/build/build-next-isolated.mjs and
# inherited by every spawned build worker so the DB layer returns a no-op stub instead
# of loading the native better-sqlite3 addon (which aborts the worker on exit).
# Never set this for the running server. Used by: src/lib/buildPhase.ts, src/lib/db/core.ts
# OMNIROUTE_BUILDING=1
# Encryption key for SQLite database encryption at rest. # Encryption key for SQLite database encryption at rest.
# Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database. # Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database.
# Generate: openssl rand -hex 32 | Leave empty to disable DB encryption. # Generate: openssl rand -hex 32 | Leave empty to disable DB encryption.

View File

@@ -1,5 +1,9 @@
{ {
"_comment": "Allowlist anti-slopsquatting (check-deps.mjs). Toda dep nova exige adicao EXPLICITA aqui apos verificar que e legitima.", "_comment": "Allowlist anti-slopsquatting (check-deps.mjs). Toda dep nova exige adicao EXPLICITA aqui apos verificar que e legitima.",
"_justifications": {
"@testing-library/dom": "Peer dep obrigatoria de @testing-library/react v16 (adicionada no PR #11224); Refs #9985.",
"@testing-library/user-event": "Utilitario oficial do ecossistema testing-library para testes de UI (adicionada no PR #11224); Refs #9985."
},
"allowed": [ "allowed": [
"@atjsh/llmlingua-2", "@atjsh/llmlingua-2",
"@aws-sdk/client-bedrock-runtime", "@aws-sdk/client-bedrock-runtime",
@@ -20,8 +24,10 @@
"@stryker-mutator/tap-runner", "@stryker-mutator/tap-runner",
"@swc/helpers", "@swc/helpers",
"@tailwindcss/postcss", "@tailwindcss/postcss",
"@testing-library/dom",
"@testing-library/jest-dom", "@testing-library/jest-dom",
"@testing-library/react", "@testing-library/react",
"@testing-library/user-event",
"@toon-format/toon", "@toon-format/toon",
"@types/better-sqlite3", "@types/better-sqlite3",
"@types/bun", "@types/bun",

View File

@@ -1,19 +1,19 @@
--- ---
title: "CLI Tools — OmniRoute" title: "CLI Tools — OmniRoute"
version: 3.8.50 version: 3.8.50
lastUpdated: 2026-08-18 lastUpdated: 2026-08-23
--- ---
# CLI Tools — OmniRoute # CLI Tools — OmniRoute
Last updated: 2026-08-18 Last updated: 2026-08-23
OmniRoute integrates with three categories of CLI tools spread across three dedicated dashboard pages: OmniRoute integrates with three categories of CLI tools spread across three dedicated dashboard pages:
| Page | Route | Concept | Count | | Page | Route | Concept | Count |
| -------------- | ----------------------- | ------------------------------------------------------------------------- | ------------ | | -------------- | ----------------------- | ------------------------------------------------------------------------- | ------------ |
| **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 26 | | **CLI Code's** | `/dashboard/cli-code` | Coding tools you point at OmniRoute (Client → CLI → OmniRoute → Provider) | 26 |
| **CLI Agents** | `/dashboard/cli-agents` | Autonomous agents you point at OmniRoute (same flow, broader scope) | 8 | | **CLI Agents** | `/dashboard/cli-agents` | Autonomous agents you point at OmniRoute (same flow, broader scope) | 9 |
| **ACP Agents** | `/dashboard/acp-agents` | CLIs that OmniRoute spawns as backend via stdio/ACP (reverse flow) | see registry | | **ACP Agents** | `/dashboard/acp-agents` | CLIs that OmniRoute spawns as backend via stdio/ACP (reverse flow) | see registry |
Legacy routes redirect via 308: `/dashboard/cli-tools``/dashboard/cli-code`, `/dashboard/agents``/dashboard/acp-agents`. Legacy routes redirect via 308: `/dashboard/cli-tools``/dashboard/cli-code`, `/dashboard/agents``/dashboard/acp-agents`.

View File

@@ -88,6 +88,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `OMNIROUTE_RELEASE_REF` | `origin/main` | `scripts/build/buildProvenance.ts` | Ref the pack-artifact provenance gate checks the build SHA against (#10427). | | `OMNIROUTE_RELEASE_REF` | `origin/main` | `scripts/build/buildProvenance.ts` | Ref the pack-artifact provenance gate checks the build SHA against (#10427). |
| `OMNIROUTE_ALLOW_CANARY_BUILD` | _(unset)_ | `scripts/build/buildProvenance.ts` | Set to `1` to allow packing a build whose SHA is not on the release line, recording it as a deliberate canary instead of failing the gate (#10427). | | `OMNIROUTE_ALLOW_CANARY_BUILD` | _(unset)_ | `scripts/build/buildProvenance.ts` | Set to `1` to allow packing a build whose SHA is not on the release line, recording it as a deliberate canary instead of failing the gate (#10427). |
| `OMNIROUTE_SMOKE_API_KEY` | _(unset)_ | `scripts/ops/deploy-canary.mjs` | API key for the canary-deploy smoke probe, sent as `Authorization: Bearer` on `/v1/chat/completions`. Only used by the deploy script (#10429), never by the server. Not related to the `OMNIROUTE_SMOKE_*` variables of the opt-in CLI smoke harness (`RUN_CLI_SMOKE=1`, `OMNIROUTE_SMOKE_BASE_URL/MODEL/API_KEY_ENV/TARGETS/TIMEOUT_MS` in `tests/integration/upstream-cli-smoke.int.test.ts`) — see [CLI Integrations → Real smoke sweep](../guides/CLI-INTEGRATIONS.md). | | `OMNIROUTE_SMOKE_API_KEY` | _(unset)_ | `scripts/ops/deploy-canary.mjs` | API key for the canary-deploy smoke probe, sent as `Authorization: Bearer` on `/v1/chat/completions`. Only used by the deploy script (#10429), never by the server. Not related to the `OMNIROUTE_SMOKE_*` variables of the opt-in CLI smoke harness (`RUN_CLI_SMOKE=1`, `OMNIROUTE_SMOKE_BASE_URL/MODEL/API_KEY_ENV/TARGETS/TIMEOUT_MS` in `tests/integration/upstream-cli-smoke.int.test.ts`) — see [CLI Integrations → Real smoke sweep](../guides/CLI-INTEGRATIONS.md). |
| `OMNIROUTE_BUILDING` | _(unset)_ | `src/lib/buildPhase.ts` | Build-phase signal (#10060): set to `1` by `scripts/build/build-next-isolated.mjs` and inherited by every spawned build worker so the DB layer returns a no-op stub instead of loading the native better-sqlite3 addon (which aborts the worker on exit). Never set for the running server. |
| `OMNIROUTE_DATA_DIR` | _(unset)_ | `open-sse/executors/promptql/threadSticky.ts` | **Fallback alias** for `DATA_DIR`, checked only when `DATA_DIR` is unset. Used to locate the PromptQL executor's on-disk thread-sticky session cache (`<dir>/promptql-thread-sessions.json`); if neither var is set, the cache stays in-memory only (not persisted across restarts). | | `OMNIROUTE_DATA_DIR` | _(unset)_ | `open-sse/executors/promptql/threadSticky.ts` | **Fallback alias** for `DATA_DIR`, checked only when `DATA_DIR` is unset. Used to locate the PromptQL executor's on-disk thread-sticky session cache (`<dir>/promptql-thread-sessions.json`); if neither var is set, the cache stays in-memory only (not persisted across restarts). |
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. | | `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. | | `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |

View File

@@ -1218,7 +1218,12 @@ export async function handleChatCore({
credentials?.providerSpecificData?.preserveEncryptedReasoning === true, credentials?.providerSpecificData?.preserveEncryptedReasoning === true,
onIncompatibleReasoning: resolveIncompatibleReasoningAction({ onIncompatibleReasoning: resolveIncompatibleReasoningAction({
reasoningTransportFallback, reasoningTransportFallback,
isComboStep: Boolean(comboStepId || comboExecutionKey), // #11178 regressed combo steps whose combo record carries no explicit
// stepId/executionKey (plain model-list combos): their explicit
// `reasoningTransportFallback: "skip"` config was silently degraded to
// "drop". `isCombo` is the combo marker; step ids are optional
// finer-grained metadata that plain combos never set.
isComboStep: Boolean(isCombo) || Boolean(comboStepId || comboExecutionKey),
headers: clientRawRequest?.headers ?? null, headers: clientRawRequest?.headers ?? null,
}), }),
} }

View File

@@ -290,6 +290,11 @@ export default function EditConnectionModal({
connection.providerSpecificData?.quotaPerUnit != null connection.providerSpecificData?.quotaPerUnit != null
? String(connection.providerSpecificData.quotaPerUnit) ? String(connection.providerSpecificData.quotaPerUnit)
: ""; : "";
// Modal-open form initialization from the loaded connection (sync with an
// external system on `isOpen`); remounting the 30+ field form per
// connection id is a behavior-risking restructure out of scope here
// (#11251 follow-up, #9985).
// eslint-disable-next-line react-hooks/set-state-in-effect
setFormData({ setFormData({
name: connection.name || "", name: connection.name || "",
priority: connection.priority || 1, priority: connection.priority || 1,

View File

@@ -1221,12 +1221,6 @@
"consoleLogsSubtitle": "Console output", "consoleLogsSubtitle": "Console output",
"logsActivitySubtitle": "User activity log", "logsActivitySubtitle": "User activity log",
"healthSubtitle": "System health check", "healthSubtitle": "System health check",
"healthVerdictReady": "OmniRoute is ready",
"healthVerdictActionRequired": "Action required to restore full operation",
"healthVerdictCoolingDown": "Cooling down after recent changes",
"advancedDiagnosticsTitle": "Advanced diagnostics",
"hide": "Hide",
"show": "Show",
"costsPricingSubtitle": "Per-model pricing rules", "costsPricingSubtitle": "Per-model pricing rules",
"costsBudgetSubtitle": "Budget limits", "costsBudgetSubtitle": "Budget limits",
"costsQuotaShareSubtitle": "Share provider quotas across keys", "costsQuotaShareSubtitle": "Share provider quotas across keys",
@@ -2928,6 +2922,7 @@
"interpreter": "Open Interpreter autonomous coding agent CLI", "interpreter": "Open Interpreter autonomous coding agent CLI",
"omp": "Oh My Pi terminal coding agent", "omp": "Oh My Pi terminal coding agent",
"letta": "Letta CLI agent with persistent memory and tool use", "letta": "Letta CLI agent with persistent memory and tool use",
"prime-agent": "Prime Agent — self-improving RLM coding harness with OpenAI-compatible provider support",
"warp": "Warp AI terminal with custom provider support", "warp": "Warp AI terminal with custom provider support",
"agent-deck": "Agent Deck multi-agent orchestrator" "agent-deck": "Agent Deck multi-agent orchestrator"
}, },
@@ -4637,6 +4632,13 @@
"retry": "Retry", "retry": "Retry",
"allOperational": "All systems operational", "allOperational": "All systems operational",
"issuesDetected": "System issues detected", "issuesDetected": "System issues detected",
"healthVerdictReady": "OmniRoute is ready",
"healthVerdictActionRequired": "Action required to restore full operation",
"healthVerdictCoolingDown": "Cooling down after recent changes",
"healthSubtitle": "System health check",
"advancedDiagnosticsTitle": "Advanced diagnostics",
"hide": "Hide",
"show": "Show",
"updatedAt": "Updated {time}", "updatedAt": "Updated {time}",
"latency": "Latency", "latency": "Latency",
"latencyP50": "p50", "latencyP50": "p50",

View File

@@ -970,6 +970,13 @@
"batchTimelineCancelled": "Cancelado", "batchTimelineCancelled": "Cancelado",
"batchTokenUsage": "Uso de Token", "batchTokenUsage": "Uso de Token",
"batchMetadata": "Metadados", "batchMetadata": "Metadados",
"batchHeaderSubtitle": "Execute muitas requisições como um único job",
"batchStep1": "1 · Enviar JSONL",
"batchStep1Desc": "Adicionar requisições",
"batchStep2": "2 · Criar lote",
"batchStep2Desc": "Executar job",
"batchStep3": "3 · Obter resultados",
"batchStep3Desc": "Baixar saída",
"batchFileContents": "Conteúdo do Arquivo", "batchFileContents": "Conteúdo do Arquivo",
"batchFileUsedByCount": "Usado por {count, plural, one {# lote} other {# lotes}}", "batchFileUsedByCount": "Usado por {count, plural, one {# lote} other {# lotes}}",
"batchFilePreview": "Prévia", "batchFilePreview": "Prévia",
@@ -2905,6 +2912,7 @@
"interpreter": "CLI do agente de codificação autônomo Open Interpreter", "interpreter": "CLI do agente de codificação autônomo Open Interpreter",
"omp": "Agente de codificação de terminal Oh My Pi", "omp": "Agente de codificação de terminal Oh My Pi",
"letta": "Agente CLI Letta com memória persistente e uso de ferramentas", "letta": "Agente CLI Letta com memória persistente e uso de ferramentas",
"prime-agent": "Prime Agent — harness de codificação RLM autoevolutivo com suporte a API compatível com OpenAI",
"warp": "Terminal de IA Warp com suporte a provedor personalizado", "warp": "Terminal de IA Warp com suporte a provedor personalizado",
"agent-deck": "Orquestrador multi-agente Agent Deck" "agent-deck": "Orquestrador multi-agente Agent Deck"
}, },
@@ -3831,6 +3839,9 @@
}, },
"endpoint": { "endpoint": {
"title": "Endpoint da API", "title": "Endpoint da API",
"subtitle": "Use o endpoint compatível com OpenAI na maioria dos SDKs e ferramentas.",
"testEndpoint": "Testar endpoint →",
"advancedProtocols": "Protocolos avançados",
"available": "Endpoints Disponíveis", "available": "Endpoints Disponíveis",
"cloudProxy": "Proxy na Nuvem", "cloudProxy": "Proxy na Nuvem",
"disableConfirm": "Tem certeza que deseja desativar o proxy na nuvem?", "disableConfirm": "Tem certeza que deseja desativar o proxy na nuvem?",
@@ -4611,6 +4622,13 @@
"retry": "Tentar Novamente", "retry": "Tentar Novamente",
"allOperational": "Todos os sistemas operacionais", "allOperational": "Todos os sistemas operacionais",
"issuesDetected": "Problemas detectados no sistema", "issuesDetected": "Problemas detectados no sistema",
"healthVerdictReady": "O OmniRoute está pronto",
"healthVerdictActionRequired": "Ação necessária para restaurar a operação plena",
"healthVerdictCoolingDown": "Em resfriamento após mudanças recentes",
"healthSubtitle": "Verificação de saúde do sistema",
"advancedDiagnosticsTitle": "Diagnósticos avançados",
"hide": "Ocultar",
"show": "Mostrar",
"updatedAt": "Atualizado {time}", "updatedAt": "Atualizado {time}",
"latency": "Latência", "latency": "Latência",
"latencyP50": "p50", "latencyP50": "p50",
@@ -12035,6 +12053,7 @@
"acp": { "acp": {
"title": "ACP Agents", "title": "ACP Agents",
"phrase": "CLIs que o OmniRoute spawna como backend de execução (fluxo reverso)", "phrase": "CLIs que o OmniRoute spawna como backend de execução (fluxo reverso)",
"warning": "A maioria dos usuários pode ignorar isto — use apenas quando uma integração exigir.",
"flow": "Cliente → OmniRoute → spawn CLI (stdio/ACP) → resposta", "flow": "Cliente → OmniRoute → spawn CLI (stdio/ACP) → resposta",
"seeOther": "Ver →" "seeOther": "Ver →"
} }
@@ -13342,6 +13361,13 @@
}, },
"resilienceConnections": { "resilienceConnections": {
"title": "Resiliência de Conexão", "title": "Resiliência de Conexão",
"reassuranceTitle": "Suas conexões se recuperam automaticamente",
"reassuranceDetail": "Normalmente nenhuma ação é necessária. O OmniRoute dá uma pausa temporária em uma conexão após falhas e depois a tenta novamente com segurança.",
"plainStates": {
"healthy": "Requisições podem ser enviadas",
"coolingDown": "Tentando novamente em breve",
"lockedOut": "Precisa da sua atenção"
},
"table": { "table": {
"status": "Status", "status": "Status",
"provider": "Provedor", "provider": "Provedor",

View File

@@ -1300,13 +1300,7 @@
"open": "mở", "open": "mở",
"close": "đóng" "close": "đóng"
}, },
"noResults": "Không có kết quả", "noResults": "Không có kết quả"
"healthVerdictReady": "OmniRoute đã sẵn sàng",
"healthVerdictActionRequired": "Cần hành động để khôi phục hoạt động đầy đủ",
"healthVerdictCoolingDown": "Đang nguội sau các thay đổi gần đây",
"advancedDiagnosticsTitle": "Chẩn đoán nâng cao",
"hide": "Ẩn",
"show": "Hiện"
}, },
"webhooks": { "webhooks": {
"title": "Webhook", "title": "Webhook",
@@ -2918,6 +2912,7 @@
"interpreter": "Tác nhân lập trình tự trị Open Interpreter CLI", "interpreter": "Tác nhân lập trình tự trị Open Interpreter CLI",
"omp": "Tác nhân lập trình Oh My Pi trên terminal", "omp": "Tác nhân lập trình Oh My Pi trên terminal",
"letta": "Tác nhân Letta CLI có bộ nhớ lâu dài và khả năng dùng công cụ", "letta": "Tác nhân Letta CLI có bộ nhớ lâu dài và khả năng dùng công cụ",
"prime-agent": "Prime Agent — bộ khung lập trình RLM tự cải tiến hỗ trợ API tương thích OpenAI",
"warp": "Terminal Warp AI hỗ trợ nhà cung cấp tùy chỉnh", "warp": "Terminal Warp AI hỗ trợ nhà cung cấp tùy chỉnh",
"agent-deck": "Trình điều phối đa tác nhân Agent Deck" "agent-deck": "Trình điều phối đa tác nhân Agent Deck"
}, },
@@ -4627,6 +4622,13 @@
"retry": "Thử lại", "retry": "Thử lại",
"allOperational": "Tất cả hệ thống đang hoạt động bình thường", "allOperational": "Tất cả hệ thống đang hoạt động bình thường",
"issuesDetected": "Phát hiện sự cố hệ thống", "issuesDetected": "Phát hiện sự cố hệ thống",
"healthVerdictReady": "OmniRoute đã sẵn sàng",
"healthVerdictActionRequired": "Cần hành động để khôi phục hoạt động đầy đủ",
"healthVerdictCoolingDown": "Đang nguội sau các thay đổi gần đây",
"healthSubtitle": "Kiểm tra tình trạng hệ thống",
"advancedDiagnosticsTitle": "Chẩn đoán nâng cao",
"hide": "Ẩn",
"show": "Hiện",
"updatedAt": "Đã cập nhật {time}", "updatedAt": "Đã cập nhật {time}",
"latency": "Độ trễ", "latency": "Độ trễ",
"latencyP50": "p50", "latencyP50": "p50",

View File

@@ -67,4 +67,5 @@ export const EXPECTED_CODE_COUNT = 21;
// +2 (#6318): "omp" (Oh My Pi) and "letta" (Letta CLI) added as agent entries. // +2 (#6318): "omp" (Oh My Pi) and "letta" (Letta CLI) added as agent entries.
// Note: #6318 originally also shipped duplicate "pi"/"jcode"/"codewhale" entries — // Note: #6318 originally also shipped duplicate "pi"/"jcode"/"codewhale" entries —
// those tools were already delivered by a separate PR, so only omp+letta landed here. // those tools were already delivered by a separate PR, so only omp+letta landed here.
export const EXPECTED_AGENT_COUNT = 8; // +1 (#11166): "prime-agent" (PrimeIntellect-ai/prime-agent) added as an agent entry.
export const EXPECTED_AGENT_COUNT = 9;

View File

@@ -89,7 +89,6 @@
"tests/unit/auth-terminal-status.test.ts", "tests/unit/auth-terminal-status.test.ts",
"tests/unit/authz/discovery-routes-local-only.test.ts", "tests/unit/authz/discovery-routes-local-only.test.ts",
"tests/unit/authz/oauth-autoimport-local-only.test.ts", "tests/unit/authz/oauth-autoimport-local-only.test.ts",
"tests/unit/quota-exhaustion-cutoff-opencode.test.ts",
"tests/unit/authz/route-guard-local-prefix.test.ts", "tests/unit/authz/route-guard-local-prefix.test.ts",
"tests/unit/authz/route-guard-skills-collect.test.ts", "tests/unit/authz/route-guard-skills-collect.test.ts",
"tests/unit/authz/route-guard-version-get-exemption.test.ts", "tests/unit/authz/route-guard-version-get-exemption.test.ts",
@@ -308,6 +307,7 @@
"tests/unit/public-client-ids-3493.test.ts", "tests/unit/public-client-ids-3493.test.ts",
"tests/unit/publicCreds.test.ts", "tests/unit/publicCreds.test.ts",
"tests/unit/qoder-oauth-config.test.ts", "tests/unit/qoder-oauth-config.test.ts",
"tests/unit/quota-exhaustion-cutoff-opencode.test.ts",
"tests/unit/quota-groups-route.test.ts", "tests/unit/quota-groups-route.test.ts",
"tests/unit/quota-key-models-route.test.ts", "tests/unit/quota-key-models-route.test.ts",
"tests/unit/quota-policy-generalization.test.ts", "tests/unit/quota-policy-generalization.test.ts",

View File

@@ -39,6 +39,10 @@ const qdrantEmbeddingModelsRoute =
// ── Helpers ── // ── Helpers ──
// Route handlers are typed against NextRequest; the management-session helper
// returns the Fetch API Request, which is structurally sufficient at runtime.
const asNextRequest = (req: Request) => req as unknown as import("next/server").NextRequest;
async function resetStorage() { async function resetStorage() {
core.resetDbInstance(); core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
@@ -91,7 +95,7 @@ test.after(async () => {
test("GET /api/settings/qdrant — returns settings with masked API key shape", async () => { test("GET /api/settings/qdrant — returns settings with masked API key shape", async () => {
const req = await makeAuthRequest("GET", "http://localhost/api/settings/qdrant"); const req = await makeAuthRequest("GET", "http://localhost/api/settings/qdrant");
const res = await qdrantSettingsRoute.GET(req as any); const res = await qdrantSettingsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -110,7 +114,7 @@ test("GET /api/settings/qdrant — returns settings with masked API key shape",
test("GET /api/settings/qdrant — 401 without auth", async () => { test("GET /api/settings/qdrant — 401 without auth", async () => {
await setRequireLogin(true); await setRequireLogin(true);
const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant"); const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant");
const res = await qdrantSettingsRoute.GET(req as any); const res = await qdrantSettingsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 401); assert.strictEqual(res.status, 401);
await setRequireLogin(false); await setRequireLogin(false);
}); });
@@ -126,7 +130,7 @@ test("PUT /api/settings/qdrant — updates settings and returns new masked shape
embeddingModel: "openai/text-embedding-3-small", embeddingModel: "openai/text-embedding-3-small",
}); });
const res = await qdrantSettingsRoute.PUT(req as any); const res = await qdrantSettingsRoute.PUT(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -148,7 +152,7 @@ test("PUT enabled=true also activates Qdrant as the engine (memoryVectorStore=qd
host: "qdrant-server", host: "qdrant-server",
collection: "c", collection: "c",
}); });
const res = await qdrantSettingsRoute.PUT(req as any); const res = await qdrantSettingsRoute.PUT(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const s = (await localDb.getSettings()) as Record<string, unknown>; const s = (await localDb.getSettings()) as Record<string, unknown>;
@@ -161,16 +165,20 @@ test("PUT enabled=true also activates Qdrant as the engine (memoryVectorStore=qd
test("PUT enabled=false resets the engine back to auto (sqlite-vec)", async () => { test("PUT enabled=false resets the engine back to auto (sqlite-vec)", async () => {
await qdrantSettingsRoute.PUT( await qdrantSettingsRoute.PUT(
(await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", { asNextRequest(
enabled: true, await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", {
host: "qdrant-server", enabled: true,
collection: "c", host: "qdrant-server",
})) as any collection: "c",
})
)
); );
await qdrantSettingsRoute.PUT( await qdrantSettingsRoute.PUT(
(await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", { asNextRequest(
enabled: false, await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", {
})) as any enabled: false,
})
)
); );
const s = (await localDb.getSettings()) as Record<string, unknown>; const s = (await localDb.getSettings()) as Record<string, unknown>;
@@ -185,9 +193,11 @@ test("PUT without the enabled field must not change memoryVectorStore", async ()
// User already on qdrant; editing only the collection must not reset the engine. // User already on qdrant; editing only the collection must not reset the engine.
await localDb.updateSettings({ memoryVectorStore: "qdrant", qdrantEnabled: true }); await localDb.updateSettings({ memoryVectorStore: "qdrant", qdrantEnabled: true });
await qdrantSettingsRoute.PUT( await qdrantSettingsRoute.PUT(
(await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", { asNextRequest(
collection: "renamed", await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", {
})) as any collection: "renamed",
})
)
); );
const s = (await localDb.getSettings()) as Record<string, unknown>; const s = (await localDb.getSettings()) as Record<string, unknown>;
@@ -211,11 +221,13 @@ test("PUT enabled=true invalidates the memory-settings cache (retrieval sees qdr
); );
const res = await qdrantSettingsRoute.PUT( const res = await qdrantSettingsRoute.PUT(
(await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", { asNextRequest(
enabled: true, await makeAuthRequest("PUT", "http://localhost/api/settings/qdrant", {
host: "qdrant-server", enabled: true,
collection: "c", host: "qdrant-server",
})) as any collection: "c",
})
)
); );
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@@ -234,7 +246,7 @@ test("PUT /api/settings/qdrant — 400 invalid settings (invalid port type in st
port: "not-a-number", port: "not-a-number",
}); });
const res = await qdrantSettingsRoute.PUT(req as any); const res = await qdrantSettingsRoute.PUT(asNextRequest(req));
assert.strictEqual(res.status, 400); assert.strictEqual(res.status, 400);
const body = await res.json(); const body = await res.json();
assert.ok(body.message || body.error, "should return error"); assert.ok(body.message || body.error, "should return error");
@@ -243,7 +255,7 @@ test("PUT /api/settings/qdrant — 400 invalid settings (invalid port type in st
test("PUT /api/settings/qdrant — 401 without auth", async () => { test("PUT /api/settings/qdrant — 401 without auth", async () => {
await setRequireLogin(true); await setRequireLogin(true);
const req = makeUnauthRequest("PUT", "http://localhost/api/settings/qdrant", { enabled: true }); const req = makeUnauthRequest("PUT", "http://localhost/api/settings/qdrant", { enabled: true });
const res = await qdrantSettingsRoute.PUT(req as any); const res = await qdrantSettingsRoute.PUT(asNextRequest(req));
assert.strictEqual(res.status, 401); assert.strictEqual(res.status, 401);
await setRequireLogin(false); await setRequireLogin(false);
}); });
@@ -257,7 +269,7 @@ test("GET /api/settings/qdrant/health — returns health result shape (qdrant di
headers: Object.fromEntries(headers.entries()), headers: Object.fromEntries(headers.entries()),
}); });
const res = await qdrantHealthRoute.GET(req as any); const res = await qdrantHealthRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -292,7 +304,7 @@ test("GET /api/settings/qdrant/health — reports named collection vector metada
try { try {
const req = await makeAuthRequest("GET", "http://localhost/api/settings/qdrant/health"); const req = await makeAuthRequest("GET", "http://localhost/api/settings/qdrant/health");
const res = await qdrantHealthRoute.GET(req as any); const res = await qdrantHealthRoute.GET(asNextRequest(req));
const body = await res.json(); const body = await res.json();
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@@ -309,7 +321,7 @@ test("GET /api/settings/qdrant/health — reports named collection vector metada
test("GET /api/settings/qdrant/health — 401 without auth", async () => { test("GET /api/settings/qdrant/health — 401 without auth", async () => {
await setRequireLogin(true); await setRequireLogin(true);
const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/health"); const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/health");
const res = await qdrantHealthRoute.GET(req as any); const res = await qdrantHealthRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 401); assert.strictEqual(res.status, 401);
await setRequireLogin(false); await setRequireLogin(false);
}); });
@@ -322,7 +334,7 @@ test("POST /api/settings/qdrant/search — returns ok + results array", async ()
topK: 5, topK: 5,
}); });
const res = await qdrantSearchRoute.POST(req as any); const res = await qdrantSearchRoute.POST(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -336,7 +348,7 @@ test("POST /api/settings/qdrant/search — 400 invalid body (empty query)", asyn
topK: 5, topK: 5,
}); });
const res = await qdrantSearchRoute.POST(req as any); const res = await qdrantSearchRoute.POST(asNextRequest(req));
assert.strictEqual(res.status, 400); assert.strictEqual(res.status, 400);
const body = await res.json(); const body = await res.json();
assert.ok(body.message || body.error, "should return error"); assert.ok(body.message || body.error, "should return error");
@@ -346,7 +358,7 @@ test("POST /api/settings/qdrant/search — 400 invalid body (empty query)", asyn
test("POST /api/settings/qdrant/cleanup — returns ok + deletedCount + retentionDays", async () => { test("POST /api/settings/qdrant/cleanup — returns ok + deletedCount + retentionDays", async () => {
const req = await makeAuthRequest("POST", "http://localhost/api/settings/qdrant/cleanup"); const req = await makeAuthRequest("POST", "http://localhost/api/settings/qdrant/cleanup");
const res = await qdrantCleanupRoute.POST(req as any); const res = await qdrantCleanupRoute.POST(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -365,7 +377,7 @@ test("GET /api/settings/qdrant/embedding-models — returns models array", async
headers: Object.fromEntries(headers.entries()), headers: Object.fromEntries(headers.entries()),
}); });
const res = await qdrantEmbeddingModelsRoute.GET(req as any); const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req));
// 200 expected; verify shape // 200 expected; verify shape
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
@@ -387,7 +399,7 @@ test("GET /api/settings/qdrant/embedding-models — lists only configured provid
headers: Object.fromEntries(headers.entries()), headers: Object.fromEntries(headers.entries()),
}); });
const res = await qdrantEmbeddingModelsRoute.GET(req as any); const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const body = await res.json(); const body = await res.json();
assert.ok(body.models.length > 0, "should list models for configured provider"); assert.ok(body.models.length > 0, "should list models for configured provider");
@@ -400,7 +412,7 @@ test("GET /api/settings/qdrant/embedding-models — lists only configured provid
test("GET /api/settings/qdrant/embedding-models — 401 without auth", async () => { test("GET /api/settings/qdrant/embedding-models — 401 without auth", async () => {
await setRequireLogin(true); await setRequireLogin(true);
const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/embedding-models"); const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/embedding-models");
const res = await qdrantEmbeddingModelsRoute.GET(req as any); const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 401); assert.strictEqual(res.status, 401);
await setRequireLogin(false); await setRequireLogin(false);
}); });
@@ -416,7 +428,7 @@ test("Qdrant routes — error response has no stack trace in body", async () =>
body: "not-valid-json{{{", body: "not-valid-json{{{",
}); });
const res = await qdrantSettingsRoute.PUT(req as any); const res = await qdrantSettingsRoute.PUT(asNextRequest(req));
assert.ok(res.status >= 400, "should return error status"); assert.ok(res.status >= 400, "should return error status");
const body = await res.json(); const body = await res.json();

View File

@@ -6,8 +6,8 @@ import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
const { getNextFamilyFallback } = await import("../../open-sse/services/modelFamilyFallback.ts"); const { getNextFamilyFallback } = await import("../../open-sse/services/modelFamilyFallback.ts");
// Regression for #8134 — GitHub Copilot ("github", alias "gh") T5 family fallback // Regression for #8134 — GitHub Copilot ("github", alias "gh") T5 family fallback
// returned "claude-opus-4-6" verbatim even though the github registry catalog // returned "claude-opus-4-6" verbatim even though the github registry catalog at
// (Opus 4.8 / 4.8-fast / 4.7 / 4.5) has NO 4.6 tier under any dot/hyphen // the time (Opus 4.8 / 4.8-fast / 4.7 / 4.5) had NO 4.6 tier under any dot/hyphen
// notation. getNextFamilyFallback() resolved `supportedIds` from the provider's // notation. getNextFamilyFallback() resolved `supportedIds` from the provider's
// registry but only used it to try notation variants of a candidate, never to // registry but only used it to try notation variants of a candidate, never to
// filter out a candidate that is provably absent from the catalog — so the // filter out a candidate that is provably absent from the catalog — so the
@@ -18,35 +18,46 @@ const { getNextFamilyFallback } = await import("../../open-sse/services/modelFam
// skips (continue) any family candidate that has no match in supportedIds // skips (continue) any family candidate that has no match in supportedIds
// under ANY notation (hyphen, dot, or a dated-snapshot id with the date // under ANY notation (hyphen, dot, or a dated-snapshot id with the date
// suffix stripped) instead of returning it unfiltered. // suffix stripped) instead of returning it unfiltered.
//
// Fixture note: #10952 later added claude-opus-4.6 to the github registry, so
// the provably-absent tier used by the fixture moved to claude-opus-4-6-thinking
// (the ladder's first candidate after 4.6 — still absent from the catalog).
test("#8134: github claude-opus-4.8 fallback chain never returns an unsupported tier (claude-opus-4-6)", () => { test("#8134: github claude-opus fallback chain never returns an unsupported tier (claude-opus-4-6-thinking)", () => {
const github = getRegistryEntry("github"); const github = getRegistryEntry("github");
assert.ok(github, "expected the github registry entry to resolve"); assert.ok(github, "expected the github registry entry to resolve");
const githubIds = new Set(github.models.map((m) => m.id)); const githubIds = new Set(github.models.map((m) => m.id));
// Fixture assumption: #10952 added claude-opus-4.6 to the github registry, so
// the original absent-tier role moved to the 4.6-thinking variant, which the
// catalog still does NOT carry under any notation.
assert.ok( assert.ok(
!githubIds.has("claude-opus-4-6") && !githubIds.has("claude-opus-4.6"), !githubIds.has("claude-opus-4-6-thinking") && !githubIds.has("claude-opus-4.6-thinking"),
"fixture assumption broken: github registry now has a 4.6 tier" "fixture assumption broken: github registry now has a 4.6-thinking tier"
); );
// Ladder reality: 4.8 -> 4.7 -> 4.6 -> [4-6-thinking (absent), 4-5-20251101,
// sonnet-5]. The absent 4-6-thinking must be SKIPPED — the third hop resolves
// to the dated 4.5 snapshot's undated catalog entry, never to 4-6-thinking.
const tried = new Set(["github/claude-opus-4.8"]); const tried = new Set(["github/claude-opus-4.8"]);
const first = getNextFamilyFallback("github/claude-opus-4.8", tried); const hops: string[] = [];
assert.ok(first, "expected a first fallback candidate"); let current = "github/claude-opus-4.8";
const firstBareId = first.replace(/^github\//, ""); for (let hop = 0; hop < 3; hop++) {
assert.ok( const next = getNextFamilyFallback(current, tried);
githubIds.has(firstBareId), assert.ok(next, `hop ${hop + 1}: family must not be silently exhausted`);
`first fallback "${first}" is not in github's registered model catalog: ${[...githubIds].join(", ")}` const bareId = next!.replace(/^github\//, "");
); assert.ok(
githubIds.has(bareId),
tried.add(first); `hop ${hop + 1}: "${next}" is not in github's registered model catalog: ${[...githubIds].join(", ")}`
const second = getNextFamilyFallback(first, tried); );
assert.ok(second, "expected a second fallback candidate (family must not be silently exhausted)"); assert.notEqual(bareId, "claude-opus-4-6-thinking");
const secondBareId = second.replace(/^github\//, ""); assert.notEqual(bareId, "claude-opus-4.6-thinking");
assert.ok( tried.add(next!);
githubIds.has(secondBareId), hops.push(next!);
`second fallback "${second}" is not in github's registered model catalog: ${[...githubIds].join(", ")}` current = next!;
); }
assert.notEqual(secondBareId, "claude-opus-4-6"); // The skip specifically fired: the 4.6 -> next hop jumped past the absent
assert.notEqual(secondBareId, "claude-opus-4.6"); // 4-6-thinking tier straight to a catalogued model.
assert.equal(hops[2].replace(/^github\//, ""), "claude-opus-4.5");
}); });
test("#8134: getNextFamilyFallback never returns a candidate absent from the resolved provider's catalog", () => { test("#8134: getNextFamilyFallback never returns a candidate absent from the resolved provider's catalog", () => {

View File

@@ -61,6 +61,11 @@ function hasImporter(mod: string, roots: string[]): boolean {
new RegExp(`(?:import|require)\\s*\\(\\s*['""][^'"]+/db/${escaped}['"]`), new RegExp(`(?:import|require)\\s*\\(\\s*['""][^'"]+/db/${escaped}['"]`),
// dynamic template: import(`…/db/<mod>.ts`) — bin/cli/runtime.mjs uses template literals // dynamic template: import(`…/db/<mod>.ts`) — bin/cli/runtime.mjs uses template literals
new RegExp(`import\\s*\\(\`[^'"\`]+/db/${escaped}\\.ts\`\\)`), new RegExp(`import\\s*\\(\`[^'"\`]+/db/${escaped}\\.ts\`\\)`),
// dynamic via file:// URL helper: import(projectFileUrl("…/db/<mod>.ts")) —
// bin/cli/runtime.mjs since #11238 (Windows-safe file:// dynamic imports).
new RegExp(
`import\\s*\\(\\s*projectFileUrl\\(\\s*['""][^'"]+/db/${escaped}\\.ts['"]\\s*\\)\\s*\\)`
),
// relative import within db/: from "./<mod>" or from "./<mod>" // relative import within db/: from "./<mod>" or from "./<mod>"
new RegExp(`from\\s+['"]\\.\\.?/${escaped}['"]`), new RegExp(`from\\s+['"]\\.\\.?/${escaped}['"]`),
]; ];

View File

@@ -41,8 +41,8 @@ test("CLI_TOOLS total code entries (including none) equals 26 (21 visible + 5 no
assert.equal(codeAll.length, 26, `Expected 26 total code entries, got ${codeAll.length}`); assert.equal(codeAll.length, 26, `Expected 26 total code entries, got ${codeAll.length}`);
}); });
test("CLI_TOOLS total (code + agent) = 34", () => { test("CLI_TOOLS total (code + agent) = 35", () => {
assert.equal(all.length, 34, `Expected 34 total entries, got ${all.length}`); assert.equal(all.length, 35, `Expected 35 total entries, got ${all.length}`);
}); });
test("All code-none entries have configType mitm OR are legacy excluded entries", () => { test("All code-none entries have configType mitm OR are legacy excluded entries", () => {
@@ -99,7 +99,7 @@ test("The 21 visible code entries include Qwen Code's rebuilt integration", () =
} }
}); });
test("The 8 agent entries match D15 list exactly (+ omp + letta, #6318)", () => { test("The 9 agent entries match D15 list exactly (+ omp + letta #6318, + prime-agent #11166)", () => {
const d15Agents = new Set([ const d15Agents = new Set([
"hermes-agent", "hermes-agent",
"openclaw", "openclaw",
@@ -109,6 +109,7 @@ test("The 8 agent entries match D15 list exactly (+ omp + letta, #6318)", () =>
"agent-deck", "agent-deck",
"omp", "omp",
"letta", "letta",
"prime-agent",
]); ]);
const agentIds = new Set(agentAll.map((t) => t.id)); const agentIds = new Set(agentAll.map((t) => t.id));
for (const id of d15Agents) { for (const id of d15Agents) {

View File

@@ -74,6 +74,9 @@ describe("omniroute setup opencode", () => {
// Commander turns `--base-url` into `baseUrl` — the runner must accept it. // Commander turns `--base-url` into `baseUrl` — the runner must accept it.
baseUrl: "http://10.0.0.5:20128", baseUrl: "http://10.0.0.5:20128",
nonInteractive: true, nonInteractive: true,
// These tests exercise the plugin install/merge path, not the container
// guard (#10057) — keep them hermetic on container devboxes/CI.
allowContainerWrite: true,
}); });
assert.equal(r.exitCode, 0); assert.equal(r.exitCode, 0);
@@ -99,6 +102,7 @@ describe("omniroute setup opencode", () => {
configDir: CONFIG_DIR, configDir: CONFIG_DIR,
baseUrl: "http://10.0.0.9:20128", baseUrl: "http://10.0.0.9:20128",
nonInteractive: true, nonInteractive: true,
allowContainerWrite: true,
}); });
assert.equal(r.exitCode, 0); assert.equal(r.exitCode, 0);
@@ -127,7 +131,11 @@ describe("omniroute setup opencode", () => {
}) })
); );
const r = await runSetupOpenCodeCommand({ configDir: CONFIG_DIR, nonInteractive: true }); const r = await runSetupOpenCodeCommand({
configDir: CONFIG_DIR,
nonInteractive: true,
allowContainerWrite: true,
});
assert.equal(r.exitCode, 0); assert.equal(r.exitCode, 0);
const cfg = readConfig(); const cfg = readConfig();
@@ -140,7 +148,11 @@ describe("omniroute setup opencode", () => {
it("fails with a clear error (exit 1) when the bundled plugin dist is missing", async () => { it("fails with a clear error (exit 1) when the bundled plugin dist is missing", async () => {
fs.rmSync(path.join(FAKE_PLUGIN_DIR, "dist"), { recursive: true, force: true }); fs.rmSync(path.join(FAKE_PLUGIN_DIR, "dist"), { recursive: true, force: true });
try { try {
const r = await runSetupOpenCodeCommand({ configDir: CONFIG_DIR, nonInteractive: true }); const r = await runSetupOpenCodeCommand({
configDir: CONFIG_DIR,
nonInteractive: true,
allowContainerWrite: true,
});
assert.equal(r.exitCode, 1); assert.equal(r.exitCode, 1);
} finally { } finally {
makeFakePluginDist(); makeFakePluginDist();

View File

@@ -15,6 +15,10 @@ const originalFetch = globalThis.fetch;
const originalJwtSecret = process.env.JWT_SECRET; const originalJwtSecret = process.env.JWT_SECRET;
const originalApiKeySecret = process.env.API_KEY_SECRET; const originalApiKeySecret = process.env.API_KEY_SECRET;
const originalXdg = process.env.XDG_CONFIG_HOME; const originalXdg = process.env.XDG_CONFIG_HOME;
const originalAllowContainerWrite = process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE;
// This test exercises the apply/merge path, not the container guard (#10057) —
// keep it hermetic on container devboxes/CI.
process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE = "1";
const testRoots = new Set<string>(); const testRoots = new Set<string>();
async function createAuthCookie(): Promise<string> { async function createAuthCookie(): Promise<string> {
@@ -72,6 +76,9 @@ test.afterEach(async () => {
else process.env.API_KEY_SECRET = originalApiKeySecret; else process.env.API_KEY_SECRET = originalApiKeySecret;
if (originalXdg === undefined) delete process.env.XDG_CONFIG_HOME; if (originalXdg === undefined) delete process.env.XDG_CONFIG_HOME;
else process.env.XDG_CONFIG_HOME = originalXdg; else process.env.XDG_CONFIG_HOME = originalXdg;
if (originalAllowContainerWrite === undefined)
delete process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE;
else process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE = originalAllowContainerWrite;
for (const root of testRoots) await fs.rm(root, { recursive: true, force: true }); for (const root of testRoots) await fs.rm(root, { recursive: true, force: true });
testRoots.clear(); testRoots.clear();
}); });

View File

@@ -11,6 +11,7 @@ test("CLI_TOOLS registry contains all expected tools including rebuilt Qwen Code
// (CodeWhale is the actively-maintained successor to DeepSeek TUI). // (CodeWhale is the actively-maintained successor to DeepSeek TUI).
// omp + letta added by #6318 (agent-category CLI integrations). // omp + letta added by #6318 (agent-category CLI integrations).
// grok-build added — xAI Grok Build TUI coding agent (ported from upstream decolua/9router#2571). // grok-build added — xAI Grok Build TUI coding agent (ported from upstream decolua/9router#2571).
// prime-agent added by #11166 (PrimeIntellect-ai/prime-agent, agent category).
const expected = [ const expected = [
"claude", "claude",
"codex", "codex",
@@ -46,6 +47,7 @@ test("CLI_TOOLS registry contains all expected tools including rebuilt Qwen Code
"grok-build", "grok-build",
"qwen", "qwen",
"zcode", "zcode",
"prime-agent",
]; ];
for (const id of expected) { for (const id of expected) {
assert.ok(id in CLI_TOOLS, `Missing tool: ${id}`); assert.ok(id in CLI_TOOLS, `Missing tool: ${id}`);

View File

@@ -106,7 +106,9 @@ test("CLI fingerprint preserves Codex executor User-Agent and maps legacy Copilo
{ model: "gpt-4o", messages: [] } { model: "gpt-4o", messages: [] }
); );
assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/0.54.0"); // #10952 bumped GITHUB_COPILOT_CLI_VERSION 0.54.0 -> 1.0.81-6; the fingerprint
// pin tracks the advertised upstream CLI version.
assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/1.0.81-6");
}); });
test("CLI fingerprint keeps legacy Copilot settings functional without exposing duplicate UI toggles", () => { test("CLI fingerprint keeps legacy Copilot settings functional without exposing duplicate UI toggles", () => {

View File

@@ -42,6 +42,9 @@ test("setup-qwen writes current V4 settings and only its dedicated env key", asy
configPath: settingsPath, configPath: settingsPath,
envPath, envPath,
yes: true, yes: true,
// These tests exercise the merge/write logic, not the container guard
// (#10057) — keep them hermetic on container devboxes/CI.
allowContainerWrite: true,
}); });
assert.equal(code, 0); assert.equal(code, 0);
@@ -76,6 +79,8 @@ test("setup-qwen does not overwrite an invalid settings file", async () => {
model: "model-id", model: "model-id",
configPath: settingsPath, configPath: settingsPath,
yes: true, yes: true,
// See above — hermetic regardless of container detection (#10057).
allowContainerWrite: true,
}); });
assert.equal(code, 1); assert.equal(code, 1);
assert.equal(await fs.readFile(settingsPath, "utf8"), "{ invalid JSON"); assert.equal(await fs.readFile(settingsPath, "utf8"), "{ invalid JSON");

View File

@@ -497,7 +497,13 @@ test(
} }
); );
test("build phase uses an in-memory database without creating sqlite files", serial, async () => { test("build phase returns the no-op stub without creating sqlite files", serial, async () => {
// Contract changed by #10060 (via #10952): the build phase no longer opens a
// real in-memory SQLite with migrations — loading the native better-sqlite3
// addon aborts the Next.js build worker on exit (node::
// RemoveEnvironmentCleanupHook). getDbInstance() now returns a no-op stub
// (pinned by tests/unit/build/10060-build-sqlite-stub.test.ts); queries are
// harmless no-ops and no file is touched.
const dataDir = makeTempDir("omniroute-db-build-"); const dataDir = makeTempDir("omniroute-db-build-");
try { try {
@@ -510,13 +516,15 @@ test("build phase uses an in-memory database without creating sqlite files", ser
const core = await importFresh("src/lib/db/core.ts"); const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance(); const db = core.getDbInstance();
assert.ok( assert.notEqual(db.driver, "better-sqlite3");
assert.equal(
db db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?") .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("provider_connections") .get("provider_connections"),
undefined,
"the build stub must answer queries with no-ops, never a real table scan"
); );
assert.equal(fs.existsSync(path.join(dataDir, "storage.sqlite")), false); assert.equal(fs.existsSync(path.join(dataDir, "storage.sqlite")), false);
assert.equal(db.pragma("journal_mode", { simple: true }), "memory");
core.resetDbInstance(); core.resetDbInstance();
} }

View File

@@ -4,7 +4,7 @@
* response (/api/v1/models), including the learned-only variant entry. * response (/api/v1/models), including the learned-only variant entry.
* Never "fix" this test by injecting the same string on both sides. * Never "fix" this test by injecting the same string on both sides.
*/ */
import { test, after, beforeEach } from "node:test"; import { test } from "node:test";
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import fs from "node:fs"; import fs from "node:fs";
import os from "node:os"; import os from "node:os";

View File

@@ -98,7 +98,11 @@ test("single-target Codex combo advertises a larger model context override", asy
assert.equal(response.status, 200); assert.equal(response.status, 200);
assert.equal(direct?.context_length, contextWindow); assert.equal(direct?.context_length, contextWindow);
assert.equal(combo?.context_length, contextWindow); assert.equal(combo?.context_length, contextWindow);
assert.equal(combo?.max_input_tokens, 272000); // #11179 raised the static codex catalog cap to maxInputTokens=872000 (the real
// usable window; the old 272000 was just the first pricing tier). The input cap
// can never exceed the total window, so with the 500K override it clamps to it:
// min(872000, 500000) = 500000.
assert.equal(combo?.max_input_tokens, 500000);
} finally { } finally {
contextOverrides.removeModelContextOverride("codex", modelId); contextOverrides.removeModelContextOverride("codex", modelId);
} }

View File

@@ -181,10 +181,11 @@ test("provider models route merges live Codex models with the local catalog then
// merge conservatively — the smaller of live vs. pinned wins, never the // merge conservatively — the smaller of live vs. pinned wins, never the
// larger, so a stale/inflated live number can never make OmniRoute promise // larger, so a stale/inflated live number can never make OmniRoute promise
// more context than the account can actually serve (#7012). Here the pinned // more context than the account can actually serve (#7012). Here the pinned
// GPT-5.6 Codex contract (272000/128000, see GPT_5_6_CODEX_CAPABILITIES) // GPT-5.6 Codex contract (872000/128000, see GPT_5_6_CODEX_CAPABILITIES — raised
// from the old 272K pricing tier to the real usable window by #11179)
// is smaller than the live payload's 999999/999999, so the pinned value wins. // is smaller than the live payload's 999999/999999, so the pinned value wins.
assert.equal(liveModel?.name, "GPT 5.6 Sol Live"); assert.equal(liveModel?.name, "GPT 5.6 Sol Live");
assert.equal(liveModel?.inputTokenLimit, 272000); assert.equal(liveModel?.inputTokenLimit, 872000);
assert.equal(liveModel?.outputTokenLimit, 128000); assert.equal(liveModel?.outputTokenLimit, 128000);
assert.equal(liveModel?.apiFormat, "responses"); assert.equal(liveModel?.apiFormat, "responses");
assert.deepEqual(liveModel?.supportedEndpoints, ["responses"]); assert.deepEqual(liveModel?.supportedEndpoints, ["responses"]);

View File

@@ -420,25 +420,24 @@ test("v1 search POST preserves stored SearXNG baseUrl for authless providers", a
} }
}); });
test("v1 search POST returns 400 when auto-select finds no configured provider (searxng-search is now fallbackOnly)", async () => { test("v1 search POST falls back to duckduckgo-free when no provider is configured (#11097)", async () => {
// Contract changed by PR #11097 ("fix(search): fall back to duckduckgo-free when
// no search provider is configured"): zero-credential /v1/search no longer returns
// 400 — it promotes the fallback-only duckduckgo-free provider so out-of-the-box
// search works. This test pins the NEW contract.
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
let capturedUrl = ""; let capturedUrl = "";
// DuckDuckGo lite HTML shape: result link + snippet cell (see
// open-sse/services/freeWebSearch.ts parseDuckDuckGoLite).
const liteHtml = `<html><body>
<a href="https://example.com/auto-result" class='result-link'>Auto-selected DuckDuckGo result</a>
<td class='result-snippet'>Fallback free search snippet</td>
</body></html>`;
globalThis.fetch = async (url) => { globalThis.fetch = async (url) => {
capturedUrl = String(url); capturedUrl = String(url);
return new Response( return new Response(liteHtml, { status: 200, headers: { "content-type": "text/html" } });
JSON.stringify({
results: [
{
title: "Auto-selected SearXNG result",
url: "https://searx.example/auto",
content: "Auto-selected self-hosted response",
engines: ["duckduckgo"],
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}; };
try { try {
@@ -454,14 +453,15 @@ test("v1 search POST returns 400 when auto-select finds no configured provider (
); );
const body = (await response.json()) as any; const body = (await response.json()) as any;
assert.equal(response.status, 400); assert.equal(response.status, 200);
assert.equal(capturedUrl, "", "fallback-only SearXNG must not receive an upstream request"); assert.equal(
assert.ok(body.error?.message || body.error); capturedUrl,
assert.match( "https://lite.duckduckgo.com/lite/",
String(body.error?.message ?? body.error), "the fallback must call the DuckDuckGo lite endpoint"
/provider|configured/i,
"the response must explain that no provider was selected"
); );
assert.equal(body.provider, "duckduckgo-free");
assert.equal(body.results[0].title, "Auto-selected DuckDuckGo result");
assert.equal(body.results[0].url, "https://example.com/auto-result");
} finally { } finally {
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
} }

View File

@@ -72,10 +72,12 @@ test("usage service covers GitHub free-plan parsing, auth denial and unsupported
assert.equal(freeUsage.quotas.completions.used, 0); assert.equal(freeUsage.quotas.completions.used, 0);
assert.equal(freeUsage.quotas.completions.remainingPercentage, 100); assert.equal(freeUsage.quotas.completions.remainingPercentage, 100);
assert.equal(calls[0].headers.Authorization, "token gho-free"); assert.equal(calls[0].headers.Authorization, "token gho-free");
assert.equal(calls[0].headers["User-Agent"], "GitHubCopilotChat/0.54.0"); // #10952 re-based the Copilot wire identity on the live-captured CLI 1.0.81-6
assert.equal(calls[0].headers["Editor-Version"], "vscode/1.126.0"); // (copilot-developer-cli integration id; API version 2026-08-01).
assert.equal(calls[0].headers["Editor-Plugin-Version"], "copilot-chat/0.54.0"); assert.equal(calls[0].headers["User-Agent"], "GitHubCopilotChat/1.0.81-6");
assert.equal(calls[0].headers["X-GitHub-Api-Version"], "2026-06-01"); assert.equal(calls[0].headers["Editor-Version"], "copilot/1.0.81-6");
assert.equal(calls[0].headers["Editor-Plugin-Version"], "copilot-chat/1.0.81-6");
assert.equal(calls[0].headers["X-GitHub-Api-Version"], "2026-08-01");
globalThis.fetch = async () => new Response("forbidden", { status: 403 }); globalThis.fetch = async () => new Response("forbidden", { status: 403 });
const forbidden: any = await usageService.getUsageForProvider({ const forbidden: any = await usageService.getUsageForProvider({

View File

@@ -128,9 +128,11 @@ test("vscode raw models route exposes native GPT-5.6 IDs and effort tiers", asyn
assert.equal(typeof defaultModel.created, "number"); assert.equal(typeof defaultModel.created, "number");
assert.equal(defaultModel.owned_by, "codex"); assert.equal(defaultModel.owned_by, "codex");
assert.equal(defaultModel.name, "Codex GPT 5.6 Sol"); assert.equal(defaultModel.name, "Codex GPT 5.6 Sol");
assert.equal(defaultModel.context_length, 272000); // #11179: codex static catalog advertises the usable 872K window (max_context_window),
// not the old 272K pricing tier.
assert.equal(defaultModel.context_length, 872000);
assert.equal(defaultModel.max_output_tokens, 128000); assert.equal(defaultModel.max_output_tokens, 128000);
assert.equal(defaultModel.max_input_tokens, 272000); assert.equal(defaultModel.max_input_tokens, 872000);
assert.deepEqual(defaultModel.capabilities, { assert.deepEqual(defaultModel.capabilities, {
vision: true, vision: true,
tool_calling: true, tool_calling: true,

View File

@@ -255,7 +255,9 @@ test("vscode combos route resolves combo names through Ollama api/show", async (
assert.equal(body.model, "show-combo"); assert.equal(body.model, "show-combo");
assert.equal(body.modelfile, "FROM show-combo"); assert.equal(body.modelfile, "FROM show-combo");
assert.equal(body.details.family, "show-combo"); assert.equal(body.details.family, "show-combo");
assert.equal(body.model_info.context_length, 272000); // #11179: codex static catalog advertises the usable 872K window (max_context_window),
// not the old 272K pricing tier.
assert.equal(body.model_info.context_length, 872000);
assert.deepEqual(body.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]); assert.deepEqual(body.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]);
assert.equal(body.model_info.capabilities.reasoning, true); assert.equal(body.model_info.capabilities.reasoning, true);
}); });
@@ -290,7 +292,8 @@ test("vscode tokenized combos root route exposes importable combo metadata", asy
assert.equal(response.status, 200); assert.equal(response.status, 200);
assert.ok(combo, "expected balanced-load in combo root response"); assert.ok(combo, "expected balanced-load in combo root response");
assert.equal(combo.url.includes("/responses#models.ai.azure.com"), true); assert.equal(combo.url.includes("/responses#models.ai.azure.com"), true);
assert.equal(combo.maxInputTokens, 272000); // #11179: codex static catalog maxInputTokens is now the usable 872K window.
assert.equal(combo.maxInputTokens, 872000);
assert.equal(combo.toolCalling, true); assert.equal(combo.toolCalling, true);
assert.deepEqual(combo.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]); assert.deepEqual(combo.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]);
}); });
@@ -1073,7 +1076,9 @@ test("vscode tokenized api/show route exposes explicit reasoning effort metadata
assert.equal(body.configurationSchema?.properties?.reasoningEffort?.default, "low"); assert.equal(body.configurationSchema?.properties?.reasoningEffort?.default, "low");
assert.equal(body.model_info["general.basename"], "Codex GPT 5.6 Sol (Default)"); assert.equal(body.model_info["general.basename"], "Codex GPT 5.6 Sol (Default)");
assert.equal(body.model_info["general.architecture"], "codex"); assert.equal(body.model_info["general.architecture"], "codex");
assert.equal(body.model_info["codex.context_length"], 272000); // #11179: codex static catalog advertises the usable 872K window (max_context_window),
// not the old 272K pricing tier.
assert.equal(body.model_info["codex.context_length"], 872000);
assert.deepEqual(body.model_info.supports_reasoning_effort, [ assert.deepEqual(body.model_info.supports_reasoning_effort, [
"low", "low",
"medium", "medium",