checkForUpdates() is fired unawaited from a setTimeout at startup. The
underlying autoUpdater.checkForUpdates() rejects on a 404 (release update
manifest not published yet), offline, or rate-limit — and the uncaught
rejection surfaced as an "Unhandled Rejection", which the packaged-app smoke
test treats as fatal (failed the macOS-intel v3.8.13 build; passed elsewhere
only by timing race). The autoUpdater "error" event still notifies the user;
wrap the await so the promise rejection never escapes. Adds a regression test.
POST /api/providers fires a credential-bearing self-fetch to the new
connection's /sync-models route (forwarding the management cookie + internal
sync auth headers). #3267 built that origin from new URL(request.url).origin —
the client-controlled Host header — so a (management-authenticated) caller
could redirect the internal request to an arbitrary host, exfiltrating the
internal sync auth token (CodeQL js/request-forgery, critical, alert #323).
Derive the origin from the trusted loopback/env-pinned base URL via a new
getModelSyncInternalBaseUrl() helper (same source the model-sync scheduler
already uses), never from the incoming request. Adds a regression test.
instrumentation-node.ts imported the #3292 cookie auto-refresh daemon via
"@/open-sse/services/autoRefreshDaemon". The @/ alias maps to src/, but the
daemon lives in the open-sse workspace, so the import resolved to the
non-existent src/open-sse/... and threw "Cannot find module" at runtime in the
built standalone. A try/catch made it non-fatal (the daemon silently never
ran), which kept typecheck and the dev server green, but the packaged Electron
app's strict startup-log smoke test failed on the "Cannot find module" line.
Use the correct @omniroute/open-sse alias, plus a regression test banning
@/open-sse/* imports across src/.
#3292 added electron/loginManager.js and a require("./loginManager") in
main.js but did not add it to electron-builder's build.files allowlist, so
the packaged app crashed at startup with "Cannot find module './loginManager'"
on the Linux/macOS smoke tests (v3.8.13 Electron release fragment).
Add loginManager.js to build.files, plus a regression test that asserts every
local require("./x") in the Electron entry points is shipped.
* chore(release): open v3.8.13 development cycle
Bump 3.8.12 → 3.8.13 across package.json, lockfile, electron/, open-sse/, and
docs/reference/openapi.yaml; add the [3.8.13] cycle placeholder to the root
CHANGELOG and the 41 i18n mirrors. Integration branch for the v3.8.13 cycle —
fixes/features land here via per-issue PRs and it merges to main at release time.
* fix(ci): skip auto-deploy when VPS host is unreachable from the runner (#3299)
Integrated into release/v3.8.13
* fix(dev): auto-rebuild better-sqlite3 on Node ABI mismatch at dev startup (#3301)
Integrated into release/v3.8.13
* feat(api): accept path-scoped API keys on client API routes (#3300)
Integrated into release/v3.8.13
* fix(sse): harden against empty responses causing Copilot Chat failures (#3297)
Integrated into release/v3.8.13
* fix(api): remove Completions.me rickroll provider (discussion #3293) (#3302)
Integrated into release/v3.8.13
* fix(opencode-provider): extract contextLength from live model catalog (#3298)
Integrated into release/v3.8.13
* feat(web-cookie): self-service login infrastructure + auto-refresh daemon (#3292)
Integrated into release/v3.8.13
* docs(changelog): record the v3.8.13 PRs merged this round (#3292/#3300/#3297/#3298/#3301/#3302/#3299)
* fix(auth): harden URL token extraction — drop query-string fallback, gate to client routes (security follow-up to #3300) (#3309)
Security follow-up to #3300 — integrated into release/v3.8.13
* docs: rename resolve-issues → review-issues skill references
* fix(dashboard): keep no-auth providers visible under 'Show configured only' (#3290) (#3312)
no-auth providers (opencode, duckduckgo-web, theoldllm, veoaifree-web) never
create a DB connection row so stats.total stays 0, which the configured-only
filter treated as 'unconfigured' and hid them — even though they are always
usable and appear unconditionally in /v1/models. filterConfiguredProviderEntries
now treats displayAuthType === 'no-auth' as configured.
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
* fix(cli): resolve update paths relative to script + recursive backup (#3295) (#3313)
omniroute update always failed on a global install:
- getCurrentVersion() read package.json from process.cwd(), which on a global
npm/brew install is the user's working dir, not the package root → null →
'Could not determine current version'.
- createBackup() resolved bin/ from cwd too, and passed the 'cli' directory to
copyFileSync → EISDIR, swallowed by the catch → 'Failed to create backup'.
Both now resolve package.json/bin relative to the script via import.meta.url,
and the backup uses cpSync({recursive:true}) so the cli/ directory is copied.
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
* fix(theoldllm): read upstream body once to avoid [502] body-already-read (#3296) (#3314)
On the cached-token path the executor never enters the refresh branch, so the
same upstream Response was read with .text() twice (token-rejection check +
final body). A Response body is single-use, so the second read threw
'Body is unusable: Body has already been read', caught and surfaced as [502].
Read the body once into finalBody and only re-read after a token-rejection
refetch.
Co-authored-by: onizukashonan14-png <onizukashonan14-png@users.noreply.github.com>
* fix(sse): strip leaked internal tool envelopes from streaming output (#3311)
Integrated into release/v3.8.13
* fix(sse): expose Claude + Gemini budget tiers in the antigravity catalog (#3184) (#3303)
Integrated into release/v3.8.13 (#3184)
* fix(catalog): compute combo context_length from known targets only (#3304)
Integrated into release/v3.8.13 — live contextLength + known-targets combo context (#3298 follow-up)
* chore(i18n): add message keys for proxy UI + vscode/ollama endpoint (#3307)
Integrated into release/v3.8.13 — i18n message keys for proxy UI + vscode/ollama
* feat(dashboard): i18n the proxy settings UI (#3310)
Integrated into release/v3.8.13 — i18n the proxy settings UI
* feat(api): model catalog enrichment + MCP model-catalog tools (#3306)
Integrated into release/v3.8.13 — model catalog enrichment + MCP model-catalog tools, reconciled with #3309 URL-token hardening
* test(catalog): align Antigravity preview-alias test with #3303 budget tiers
#3303 added the Gemini `-high`/`-low` budget tiers to ANTIGRAVITY_PUBLIC_MODELS
(user-callable on the Antigravity OAuth backend, verified via #3184), but did
not update the catalog-route test that asserted `antigravity/gemini-3.1-pro-high`
must NOT be exposed. The assertion now reflects the intended behavior — the
client-visible budget alias IS surfaced — while keeping the legacy
`gemini-claude-*` alias keys unexposed. Caught running the full catalog suite
on the merged release HEAD (the #3303 round only ran the antigravity-aliases
and usage-hardening files).
* docs(changelog): record the 6 PRs merged this review round into v3.8.13
#3306/#3307/#3310 (New Features — VS Code split: catalog+MCP, i18n keys, proxy
UI i18n), #3311/#3303/#3304 (Bug Fixes — SSE envelope sanitizer, antigravity
budget tiers, combo known-targets context_length).
* chore(release): finalize v3.8.13 changelog and cleanup
Finalize the v3.8.13 changelog with release date, maintenance notes,
and contributor credits. Update MCP docs to reference the correct tool
inventory diagram, exclude nested .claude worktrees from ESLint scans,
and tighten a response sanitizer type guard.
* fix(dashboard): refresh connections after provider auth import (#3320)
Integrated into release/v3.8.13 — refresh connections after provider auth import
* fix(codex): strip client-only params on native /responses passthrough (#3317) (#3325)
A /v1/responses request against the built-in codex/ provider does an
openai-responses -> openai-responses passthrough (CodexExecutor.transformRequest
returns the body early for _nativeCodexPassthrough). It forwarded client-only
fields verbatim and the Codex upstream rejected them with 400 Unsupported
parameter: prompt_cache_retention / safety_identifier / user — breaking Factory
Droid (which injects all three). The chat-completions path already strips these
(base.ts #1884, openai-responses translator #2770) but the passthrough skips
translation. Strip the three fields in the shared block before the passthrough
return; user is removed unconditionally since Codex /responses always rejects it.
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
* fix(dashboard): normalize agent-bridge /state response to stop page crash (#3318) (#3326)
The Agent Bridge page seeded a well-shaped initialData default then replaced it
wholesale with the raw /api/tools/agent-bridge/state response. The route returns
{ server, agents } but the UI reads { serverState, agentStates, bypassPatterns,
mappings }, so serverState became undefined and AgentBridgeServerCard crashed on
serverState.running — surfaced as the full-page 'Internal Server Error' boundary
(client render error, not a real 5xx).
Add a shared normalizeAgentBridgeState() that maps the route shape into the page
contract (server.running/certExists -> serverState) and always returns safe
defaults (never undefined serverState). Wired into both the SSR loader (page.tsx)
and the polling hook. The legacy 'agents' entry shape differs from AgentStateEntry
so it is not coerced; full route<->page contract reconciliation (port, upstreamCa,
bypassPatterns, mappings, agentStates) is a follow-up.
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
* docs: VS Code/Ollama endpoints + env & i18n tooling (#3319)
Integrated into release/v3.8.13 — VS Code/Ollama docs + env & i18n tooling
* feat(provider): test-all endpoint, rate-limit overrides, visibility f… (#3267)
Integrated into release/v3.8.13 — provider test-all endpoint, rate-limit overrides, model visibility
* feat: auto-combo optimization, playground model dropdown, only-configured toggle (#3322)
Integrated into release/v3.8.13 — auto-combo candidate expansion + playground dropdown + only-configured toggle
* feat(api): VS Code Copilot Ollama-compatible BYOK endpoint (#3316)
Integrated into release/v3.8.13 — VS Code Copilot Ollama-compatible BYOK endpoint (reconciled with #3306/#3309 auth hardening)
* chore(release): document #3320 in the v3.8.13 changelog + contributor credits
---------
Co-authored-by: Felipe Almeman <4226997+zhiru@users.noreply.github.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: uniQta <uniQta@users.noreply.github.com>
Co-authored-by: onizukashonan14-png <onizukashonan14-png@users.noreply.github.com>
Co-authored-by: tycronk20 <tycronk20@users.noreply.github.com>
Co-authored-by: Vinayrnani <vinayrnani@gmail.com>
Integrated into release/v3.8.12. Salvaged the emitHookBlocking payload-chaining fix from the now-closed plugins-v4 branch (#3221) and adapted it to the shipped release hooks.ts: each blocking handler now sees the body/metadata as mutated by previous handlers. TDD regression test included (RED before, GREEN after); existing plugins-hooks suites green (19+5), typecheck + lint clean.
Integrated into release/v3.8.12. CodeQL hardening on the Chipotle executor: Math.random → crypto.randomInt/randomUUID, and a strict URL hostname check in the test. Fixed the node:crypto import (crypto.randomInt is not on the Web Crypto global → would crash at WS-connect) and added a regression guard exercising both helpers.
A working Qoder PAT was reported as "expired". The validator probes the Cosy endpoint
(api1.qoder.sh) — which IS the correct PAT path (the executor falls back to it after the
expected 401 from api.qoder.com). The bug was the verdict: isCosyAppError (added by #2860)
marked ANY Cosy 500 with "success":false as an auth failure, including a generic
{..."msgCode":500,"message":"Internal Server Error"} server fault — contradicting the
older #1391 "5xx = valid bypass" rule.
Narrow it: a Cosy 500 only marks the PAT invalid when the body carries an EXPLICIT auth
signal (unauthorized/forbidden/expired/token invalid/...); a generic Internal Server Error
falls back to valid-bypass. #2860's protection for genuine auth rejections is preserved.
Regression test: tests/unit/qoder-cli.test.ts — the two pre-existing generic-500 cases now
assert valid:true (they encoded the #3247 bug) + a new explicit-auth-signal case asserts
valid:false. 13/13 green.
Follow-up to the #3269 private-webhook opt-in. With the opt-in on, the private-host
check was bypassed entirely, leaving cloud-metadata endpoints (169.254.169.254,
metadata.google.internal, 100.100.100.200, link-local 169.254.0.0/16) reachable — the
classic SSRF -> IAM-credential pivot — and the webhook test endpoint returned the
upstream body, making it a content-exfiltration primitive against internal services.
- outboundUrlGuard: add isCloudMetadataHost(); parseAndValidateWebhookUrl blocks those
hosts UNCONDITIONALLY, even when private targets are opted in.
- webhooks/[id]/test: redact responseBody for private targets (status + latency only).
Regression test: tests/unit/webhook-metadata-guard-3269.test.ts (RED before, GREEN after);
existing webhook SSRF/opt-in suites stay green (34/34).
Webhooks hardcoded parseAndValidatePublicUrl, which blocks any RFC1918/loopback host —
breaking self-hosted setups that legitimately point webhooks at internal services
(n8n, Home Assistant, a LAN box). Provider URLs already had an opt-in
(OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS); webhooks now reuse it.
- outboundUrlGuard: add parseAndValidateWebhookUrl — gates the private-host check on
arePrivateProviderUrlsAllowed() (default OFF); protocol + embedded-credential checks
stay unconditional.
- swap all webhook call sites (create/update/test/validate-url + dispatcher x2) to it.
Regression test: tests/unit/webhook-private-optin-3269.test.ts (RED before, GREEN after);
existing webhook SSRF/dispatcher suites stay green (33/33).
A custom OpenAI-compatible image-edit provider received an empty `model`. In production
`globalThis.fetch` is patched with node_modules/undici's fetch, whose `FormData` class
differs from `globalThis.FormData`; passing a native FormData made undici serialize it as
the string "[object FormData]" (text/plain), dropping every field including `model`.
handleOpenAIImageEdit now assembles the multipart body as a Buffer with an explicit
boundary + Content-Type, which every fetch impl accepts verbatim.
Regression test: tests/unit/image-edits-multipart-3273.test.ts reproduces the exact prod
condition (routes through undici's fetch) — RED before (upstream got text/plain
[object FormData]), GREEN after. Existing image suites stay green (50/50).
Regression of #764. Claude Code → Groq (llama-3.3-70b-versatile) returned HTTP 400
because the model was treated as reasoning-capable: supportsReasoning() defaulted to true,
so applyThinkingBudget did not strip reasoning params, and the claude→openai translator
forwarded reasoning_effort (and re-injected it from output_config.effort) — which Groq
rejects on non-reasoning models.
- providerRegistry: mark llama-3.3-70b-versatile + llama-4-scout supportsReasoning:false
(gpt-oss / qwen3-32b keep reasoning — they accept reasoning_effort).
- stripThinkingConfig: also strip output_config.effort so the translator can't re-inject
reasoning_effort downstream.
Regression test: tests/unit/thinking-budget-groq-3258.test.ts (RED before, GREEN after);
existing thinking-budget suites stay green (45/45).
ds-web/deepseek-v4-pro emits tool calls wrapped as
<tool_call name="skill">{"name":"customize-opencode"}</tool_call> instead of the
canonical <tool>{json}</tool>. webTools.ts only matched <tool>...</tool>, so the block
was silently dropped (and when arguments were present, the surrounding tag leaked into
content). Add TOOL_CALL_TAG_RE to capture the JSON body — the real tool name comes from
the body, never the tag's name= attribute — and extend the early-exit + range stripping.
Regression test: tests/unit/web-tools-translation-3260.test.ts (RED before, GREEN after).
Existing web-tools suites stay green (26/26).
CodeQL flagged 7 high alerts in code the cycle touched (the large release-PR diff
re-surfaces them). Resolved at the source — no dismissals:
- fix(images): resolveImageBaseUrl trimmed trailing slashes with `/\/+$/`, a
polynomial-ReDoS pattern (js/polynomial-redos) on the configured node base URL.
Replace it with a non-backtracking endsWith/slice loop.
- test(oauth): pin the Anthropic OAuth host with exact-equality asserts and a
parsed-hostname negative check instead of substring `.includes()`
(js/incomplete-url-substring-sanitization). The exact-equality assertions were
already present, so coverage is unchanged.
- test(images): drop the redundant `!includes("generativelanguage.googleapis.com")`
assert — the exact-equality assert on the resolved URL already guarantees it.
Finalize the 3.8.11 cycle CHANGELOG and clear the failures the full test:unit
gate surfaced (release-branch drift — PR merges bypassed pre-push):
- CHANGELOG: date the [3.8.11] section (2026-06-05) + repo-housekeeping roll-up
- docs(env): document THEOLDLLM_NAV_TIMEOUT_MS in .env.example + ENVIRONMENT.md
(env-doc-sync gate; #3217 added the var without docs)
- test(nvidia): exercise the #3226 bypass-fetch path via a local HTTP server and
hoist the validator import (patching globalThis.fetch no longer intercepts the
un-patched native fetch captured by proxyFetch)
- test(i18n): import the shipped normalizeComplianceEventTypes helper (#3185)
- test(model-caps): save synced metadata under the canonical gemini-3.1-pro key
now that #3229 aliases gemini-3.1-pro-high/-low to it
- test(web-session): expect the grok-web "sso + sso-rw" credential hint (#3180)
- test(synced-models): isolate DATA_DIR so the #3199 hidden-override stops
bleeding into the shared DB and breaking the re-run precondition
agy's gemini-3.1-pro-high/-low had no alias, so resolveAntigravityModelId sent the
speculative -high/-low suffix verbatim to upstream, which rejects it (400) for
gemini-3.x. Worse, the non-stream executor branch fed the 4xx response into the SSE
collector, returning a synthetic empty {object:chat.completion} envelope that masked
the error. Alias both to gemini-3.1-pro, and surface real upstream errors via
buildErrorBody for non-ok non-stream responses. + unit tests.
The Codex CLI WS->HTTP fallback rewrite (resolveResponsesApiModel) prefixes a
bare model id with codex/ whenever codex/<id> resolves to codex. Codex accepts
arbitrary model strings, so a combo name with no slash (e.g. n8n-text,
paid-premium) was rewritten to codex/<combo> and sent to Codex instead of being
resolved as a combo — regressing combos via /v1/responses in v3.8.9+. Skip the
rewrite when the bare id is a combo (getComboByName). + unit test.
- grok-web: the credential hint named only "sso" while Grok needs both "sso"
and "sso-rw"; users pasted just sso and hit anti-bot 403s. Name both
(credentialName + placeholder). The underlying Cloudflare anti-bot 403 is
upstream and tracked separately on #3180.
- vertex: the Service Account JSON placeholder was an untranslated stub literal
("Vertex Service Account Placeholder") in 40 locales, making the field look
broken even though SA-JSON auth is fully supported. Replace with real
instructional text (zh localized; pt-BR already translated).
Guard test pins both hints.
The NVIDIA key-validation chat probe used models[0] (z-ai/glm-5.1), which
requires the 'Public API Endpoints' account permission and has DEGRADED
windows. Accounts lacking that permission see the probe hang until the
validation timeout, surfacing as a misleading 'Upstream Error' on a valid key.
Probe the universally-available meta/llama-3.1-8b-instruct instead, still
overridable via providerSpecificData.validationModelId. + unit test.
getProviderConfig falls back to { name: providerId } for unknown ids, so the
label precedence config.name || p.name let the raw internal UUID of a custom
provider shadow the friendly name HomePageClient already resolved into p.name.
Extract resolveTopologyNodeLabel (entry name first) + unit test.
Image routes now resolve a requested model the same way across /v1/images/generations
and /v1/images/edits, via a shared resolver: built-in id -> custom provider prefix ->
bare combo/alias name (e.g. "image" -> its single image target). Previously a bare
combo name fell through to "Invalid image model".
/v1/images/edits gains two capabilities for custom OpenAI-compatible providers:
- multipart edit forwarding to the node's {base_url}/images/edits (was hard-rejected
unless chatgpt-web);
- JSON/data-URL edit input (images:[{image_url:"data:..."}]), converted to the same
fields the multipart reader produces (was "Invalid multipart body").
The chatgpt-web conversation-continuation edit flow is unchanged.
The db-backups import route statically imported better-sqlite3, which is
stripped from the Next standalone server's node_modules in the packaged
Electron app. Loading the route then crashed with "Cannot find module
'better-sqlite3'" on the Windows installer, even though node:sqlite was
available. Route the upload integrity-check through openDatabaseAsync
(better-sqlite3 -> node:sqlite -> sql.js), matching every other DB path.
Adds a guard test so no API route can reintroduce a direct native import.
Follow-up to #3204: deleting a synced (fetched) model removed it from the
synced set, but the DELETE route didn't mark it hidden and the re-import path
didn't skip hidden ids, so the next auto-fetch re-added it. Now the route
marks the id hidden on delete and replaceSyncedAvailableModelsForConnection
filters hidden ids, so the deletion sticks.
Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com>
Residual of #3136: a local connection with an empty baseUrl still resolved
to this.config.baseUrl (OpenAI). Fall back to the provider's localDefault
(127.0.0.1:8080/v1) before the OpenAI default for the local-provider group.
Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com>
The healthcheck probed only 127.0.0.1 and swallowed every error (empty
Output), so containers binding to a non-loopback address always reported
unhealthy with no diagnostic. Add a multi-host probe helper that succeeds on
the first 2xx and prints the last error to stderr on total failure.
Co-authored-by: naimo84 <naimo84@users.noreply.github.com>
The image-generation handler read credentials.baseUrl (always undefined),
so custom OpenAI-compatible image providers fell back to the Gemini endpoint
(401). Resolve from providerSpecificData.baseUrl like the chat path, and
rewrite prefix/model to the internal node id before the exact-id lookup.
Co-authored-by: ngocquynh85 <ngocquynh85@users.noreply.github.com>
normalizeDiscoveredModels only copied record.inputTokenLimit, but OpenRouter
returns the window as context_length / top_provider.context_length, so every
synced model fell back to the 128K default. Read context_length (and
top_provider.max_completion_tokens for output) as a fallback.
Co-authored-by: pulyankote <pulyankote@users.noreply.github.com>