mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 19:52:50 +03:00
61e39bfe54201edd6343455672e9ad72863859de
231 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
abd4df63dc |
fix(sse): surface Qwen/Alibaba personal Token Plan quota in dashboard and preflight (#10290)
* fix(sse): surface Qwen/Alibaba personal Token Plan quota in dashboard and preflight The personal Token Plan (5-hour / 7-day sliding windows) has no official OpenAPI and the inference API key cannot read it. Add a cookie-authenticated fetcher for the console gateway shared by home.qwencloud.com and the Model Studio console (contract captured live from a logged-in session): - open-sse/services/qwenTokenPlanQuotaFetcher.ts: POST /data/api.json (IntlBroadScopeAspnGateway / sfm_bailian) for usage + quota-config + subscription; sec_token resolved best-effort from the dashboard HTML; per-window parse (fields are omitted while a window is Temporarily Removed); 60s usage cache, 1h tier cache. - usage/qwen-token-plan.ts leaf + registration in the usage dispatcher, USAGE_FETCHER_PROVIDERS, USAGE_SUPPORTED_PROVIDERS, PROVIDER_LIMITS_APIKEY_PROVIDERS and bespoke preflight/monitor windows. - Also adds bailian-coding-plan to USAGE_SUPPORTED_PROVIDERS / PROVIDER_LIMITS_APIKEY_PROVIDERS: the coding-plan fetcher existed but the dashboard filtered those connections out (UI gap). Refs #9603 (Problema 1 — quota missing; the 429 recovery half is a follow-up). * docs(env): document Qwen Token Plan quota env vars + regen omni-settings skill QWEN_CLOUD_COOKIE, QWEN_CLOUD_SEC_TOKEN, QWEN_TOKEN_PLAN_HOST and QWEN_TOKEN_PLAN_DASHBOARD_URL added to .env.example and docs/reference/ENVIRONMENT.md (check:env-doc-sync), with the generated omni-settings skill refreshed (check:agent-skills-sync). Refs #9603 * revert: keep hand-tuned omni-settings thinking-budget section The agent-skills-sync drift predates this PR (hand improvement from #10169 not yet synced into the generator source) — it fails on every open PR and belongs to a base-reds fix, not this branch. Regenerating here would erase the intentional content. * feat(dashboard): add the Qwen/Model Studio console cookie field to the connection modal The Token Plan quota fetcher is cookie-authenticated (the inference API key cannot read the console gateway), but no modal field existed to paste that cookie — so the quota was unconfigurable from the dashboard and the fetcher could only ever return its 'needs a cookie' message. Adds the field for qwen-cloud-token-plan and bailian-coding-plan alongside the existing ollama-cloud / alibaba console-cookie inputs (same password-input, blank-keeps-stored semantics), pre-fills it when editing a connection, and extends the providerSpecificData string/length validation to the two new keys. Tests: tests/unit/qwen-token-plan-cookie-field.test.ts (RED before, GREEN after) covers persistence + trimming, the blank-input no-overwrite rule and schema acceptance/rejection. Refs #9603 * docs(dashboard): correct the Qwen console cookie instructions The placeholder claimed the cookie looks like 'token=...'; the qwencloud portal actually issues 'login_qwencloud_ticket=...' alongside cna/cnaui/aui (mirroring login_aliyunid_ticket on the Alibaba console), so the hint pointed at the wrong value. Replaces the guesswork with the verified retrieval steps in all three places an operator can hit — the modal field hint, the fetcher's 'needs a cookie' message and .env.example/ENVIRONMENT.md: log in to home.qwencloud.com > Billing > Subscription, F12 > Network, reload, filter by api.json, click a request to cs-data.qwencloud.com and copy the WHOLE Cookie request header. Also documents that the value must go on one line (it contains '=' and ';') and that it dies with the browser session. Refs #9603 * fix(dashboard): tolerate partial form objects in the qwen cookie branch Adding bailian-coding-plan to QWEN_TOKEN_PLAN_PROVIDERS routed callers that previously matched NO branch in assignQuotaScrapingProviderData into the new one, which assumed the two new fields are always present. Older callers build a partial form object, so buildAddProviderSpecificData threw: TypeError: Cannot read properties of undefined (reading 'trim') (tests/unit/dashboard/agentrouter-connection-modal-fields.test.ts) Reads the new fields with optional chaining and adds a regression test that calls the helper with those keys deleted for both providers. Refs #9603 * refactor(dashboard): move quota-scraping form logic into a UI-free module tests/unit/qwen-token-plan-cookie-field.test.ts imported QuotaScrapingFields directly, which pulls `@/shared/components` and, through that barrel, untranspiled ESM (@lobehub/icons). The node:test runner cannot parse it and the whole test file died in CI with: SyntaxError: Unexpected token 'export' at @lobehub/icons/es/Ai21/components/Mono.js (It passed locally, so only the CI shard surfaced it.) Extracts the pure pieces — QWEN_TOKEN_PLAN_PROVIDERS, QuotaScrapingFieldValues, EMPTY_QUOTA_SCRAPING_FIELDS and assignQuotaScrapingProviderData — into quotaScrapingFieldValues.ts. The component imports them and re-exports the public names, so every existing importer keeps its current path. The unit test now targets the UI-free module. Refs #9603 * fix(providers): point bailian-coding-plan at the Token Plan endpoint and its console Two independent defects kept this provider unusable with a valid Alibaba Token Plan key (verified live 2026-08-14 with the owner's key and cookie): 1. Wrong inference host. The catalog entry is named "Alibaba Token Plan", links to token-plan-overview and its hint asks for a Token Plan key, but the registry pointed at coding-intl.dashscope.aliyuncs.com — the Coding Plan host, which rejects Token Plan keys with 401 invalid_api_key. The documented Anthropic base URL for Token Plan is token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic (https://www.alibabacloud.com/help/en/model-studio/more-tools). Against the new host the same key returns 200 for all six registry models and a real completion; auth stays on x-api-key. 2. Wrong console identity for quota. The personal Token Plan is sold through two consoles sharing one backend, and the gateway validates the session against the console declared in the request: an Alibaba console cookie (login_aliyunid_ticket) sent with the QwenCloud identity is refused with BailianGateway.Login.NotLogined. resolveConsoleSite() now picks host, cornerstoneParam.consoleSite/domain and Origin/Referer from the cookie's login ticket, falling back to the provider. With that switch the same cookie returns usage/subscription/quota-config. Also routes bailian-coding-plan quota through the Token Plan fetcher (the Coding Plan call returns "Bad Request" for these accounts), keeping the old fetcher as the fallback for real Coding Plan keys, and labels the plan by console ("Alibaba Token Plan (Pro)" vs "Qwen …"). Live validation: inference 200 (qwen3.7-plus answered "FUNCIONA"); quota 12,934/40,000 credits, 67.7% remaining, resets 2026-08-20. Refs #9603 --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
7837e46908 |
feat(ocr): Vertex AI DeepSeek-OCR provider (#10398)
* feat(sse): add Vertex AI DeepSeek OCR transformation to the registry Adds VERTEX_DEEPSEEK_TRANSFORMATION (request/response mapping for the Vertex AI DeepSeek OCR MaaS endpoint) and registers the "vertex-deepseek-ocr" provider in OCR_PROVIDERS, modeled on litellm's VertexAIDeepSeekOCRConfig. buildRequest treats the resolved baseUrl as the complete Vertex endpoint URL (project/location resolved upstream), matching the existing Mistral passthrough pattern. * feat(sse): resolve Vertex AI DeepSeek OCR auth and endpoint URL Adds resolveVertexOcrAccessToken (mints a Vertex OAuth access token from a Service Account JSON apiKey, reusing open-sse/executors/vertex.ts's existing JWT-bearer exchange — no new OAuth flow) and resolveVertexOcrBaseUrl (derives the project/location "openapi/chat/ completions" endpoint from providerSpecificData or the Service Account JSON's project_id). Both live in open-sse/handlers/ocr.ts, not the src/app/api/v1/ocr route, since routes may not import executor implementations directly (EXECUTOR_IMPORT_RESTRICTION in eslint.config.mjs) — the route re-exports/consumes them across that boundary. handleOcr now prefers credentials.accessToken over apiKey so the minted token (not the raw Service Account JSON) is sent upstream. * docs(api): document the vertex-deepseek-ocr /v1/ocr provider Adds the vertex-deepseek-ocr row to the /v1/ocr provider table and a short section on its Vertex AI auth/endpoint resolution, and lists the new provider/model id in openapi.yaml alongside mistral and azure-document-intelligence. * docs(skills): regenerate omni-inference skill for the Vertex OCR provider --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
ff64716c31 |
feat(dashboard): opt-in CSP relaxation for VS Code Simple Browser embedding (#10273) (#10386)
OmniRoute ships `frame-ancestors 'none'` + `X-Frame-Options: DENY` on every route, so the VS Code Simple Browser renders a blank tab — which is what the OmniCopilot extension's `dashboardOpen: "editor"` mode uses. Add the build-time opt-in `DASHBOARD_ALLOW_EMBED=vscode`. When set, the HTML pages are served with `frame-ancestors 'self' vscode-webview:` and without `X-Frame-Options` (XFO cannot express a custom scheme and would veto the relaxed CSP). Unset — the default — nothing changes. The API surface stays strictly unframable in both modes. Its exclusion list is derived from the `rewrites()` table plus `/api`, `/a2a`, `/healthz`, so a future root-level API alias is excluded automatically instead of silently becoming framable. The two generated `source` patterns are complementary by construction: every pathname matches exactly one, so there is no gap (a page with no security headers) and no order-dependent overlap. Closes #10273 Co-authored-by: Xiangzhe <bakryun0718@proton.me> |
||
|
|
c62ace5a49 |
feat(ocr): multi-provider /v1/ocr with transformation layer (Azure Document Intelligence) (#10283)
* feat(ocr): transformation layer on ocrRegistry (Mistral shape canonical)
* feat(ocr): Azure Document Intelligence provider (prebuilt-read, analyze+poll)
* feat(ocr): generic dispatch with per-provider transformation and DI poll loop
* test(ocr): align sanitized-500 assert with HR#12 error sanitization
The test's own title ("returns a sanitized 500") describes the new
behavior mandated by HR#12 (never leak err.message in a response body).
The old regex asserted the pre-sanitization leak (`OCR request failed:
socket closed`) as expected output, which contradicted its own title
and the sanitization this task intentionally introduced in
open-sse/handlers/ocr.ts. Scoped to this single assertion only.
* fix(ocr): fail fast on non-ok poll responses instead of misleading 504
pollOcrOperation now checks pollRes.ok and returns a sanitized 502
immediately (logging the upstream status via console.error) instead of
looping until the 30-attempt cap and surfacing a misleading timeout for
what was actually an auth/upstream error during polling.
* feat(ocr): route/docs for multi-provider /v1/ocr
- Route: map the connection's providerSpecificData.baseUrl onto
credentials.baseUrl (resolveOcrCredentials) so azure-document-intelligence
connections resolve their endpoint the same way every other custom-endpoint
provider does (src/lib/providers/validation/*); previously handleOcr only
saw a baseUrl when a caller set it directly, so the DB-backed Azure
connection endpoint was never forwarded.
- v1OcrSchema.model is already a free-form string, no schema change needed.
- Docs: add the /v1/ocr provider table + example + Azure poll-flow note to
API_REFERENCE.md, and describe the provider/model prefix + async poll
behavior in openapi.yaml.
- Test: tests/unit/ocr-route-contract.test.ts covers getAllOcrModels/
parseOcrModel for both providers and resolveOcrCredentials's mapping.
* chore(quality): rebaseline deadExports for the OCR/image-to-text series
* docs(skills): regenerate omni-inference skill for the multi-provider /v1/ocr
The generated agent skill mirrors docs/reference/API_REFERENCE.md; updating the
/v1/ocr section left it stale and tripped the merge-integrity gate.
---------
Co-authored-by: Xiangzhe <bakryun0718@proton.me>
|
||
|
|
081f482680 |
fix(providers): raise default provider probe timeout from 5s to 8s (#9283)
* fix(providers): raise default provider probe timeout from 5s to 8s The validationRead and modelsProbe presets in safeOutboundFetch.ts used a fixed 5000ms timeout for the periodic credential health check and on-demand connection test. Several real free-tier providers (Cerebras, Cloudflare AI observed in practice) routinely take close to 5s to answer a lightweight /models probe, which is indistinguishable from a real outage under that budget — the connection flaps between "active" and "error" in the dashboard/topology view purely from being near the edge of the timeout, not from any actual failure. Raised the default to 8000ms and made it configurable via OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS (validated: falls back to 8000ms for non-numeric or sub-1000ms values) so it can be tuned per-deployment without a code change. validationWrite and modelsPagination presets are untouched. Added tests/unit/safe-outbound-fetch-probe-timeout.test.ts covering the default, env override, invalid-value fallback, and that the other two presets are unaffected. * docs(.env.example): document OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS * Merge branch 'release/v3.8.50' into fix/provider-probe-timeout Resolved merge conflict in .env.example: kept both Provider probe section (PR) and Proxy/relay fetch section (release branch). Added docs/reference/ENVIRONMENT.md entry for OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
4e1d21f756 |
docs(settings): Thinking Budget modes + fix Auto i18n collision (#10169)
Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com> |
||
|
|
7f2d75d6d5 |
feat(open-sse): expose provider-level circuit breaker thresholds via env vars (#10040) (#10046)
The provider-level breaker fields in PROVIDER_PROFILES (providerFailureThreshold, providerFailureWindowMs, providerCooldownMs, degradationThreshold, maxBackoffMultiplier, backoffEscalationCount) are now env-overridable via OMNIROUTE_PROVIDER_BREAKER_<CATEGORY>_<FIELD> variables, with the historical hardcoded defaults preserved when unset. This makes the provider-level fuse (the entire-provider cooldown applied after repeated upstream failures) tunable from the deployment surface, matching the existing per-key circuit breaker knobs. Operators can now raise thresholds to tolerate transient upstream sheds without blacklisting the provider, or lower them to fail over faster on premium routes — without rebuilding from source. Closes #10040 Category-by-category field map (defaults preserved): - oauth: FAILURE_THRESHOLD=10, FAILURE_WINDOW_MS=900000, COOLDOWN_MS=300000, DEGRADATION_THRESHOLD=5, MAX_BACKOFF_MULTIPLIER=8, BACKOFF_ESCALATION_COUNT=2 - apikey: [REDACTED:auth_header], FAILURE_WINDOW_MS=1800000, COOLDOWN_MS=600000, DEGRADATION_THRESHOLD=7, MAX_BACKOFF_MULTIPLIER=4, BACKOFF_ESCALATION_COUNT=3 - local: FAILURE_THRESHOLD=2, FAILURE_WINDOW_MS=300000, COOLDOWN_MS=60000 (local category omits the adaptive v2 fields) Docs: - .env.example — 15 new commented entries grouped under a "Provider-level circuit breaker thresholds and cooldowns" section. - docs/reference/ENVIRONMENT.md — 15 new rows documenting the provider-level breaker surface. Tests: - tests/unit/provider-breaker-env-overrides.test.ts — 4 cases: 1. Every new env var is wired in constants.ts via envInt(). 2. Every new env var is documented in ENVIRONMENT.md. 3. Every new env var is listed in .env.example. 4. The historical defaults are preserved as the envInt fallback. Behavior tests (loading the actual module with controlled env vars) are left to upstream CI; the static source-shape test is sufficient here because the envInt() helper is a plain function whose only dependency is process.env at module load time. Co-authored-by: Tiangao (hermes) <montigaud@aikumi.pro> |
||
|
|
d925f6bf73 |
fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies (#10038)
* fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies Extracted from PR #9439 (agentic conversation tracking). Most of the original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB env var support, the estimateSizeFast() earlyExitAt parameterization) turned out to already be present on the current upstream/release/v3.8.50 tip -- confirmed via diff and by running check-env-doc-sync.test.ts / tests/unit/chatcore-log-truncation.test.ts against pristine upstream before making any changes here. Only two genuine gaps remained: 1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but undocumented in .env.example and docs/reference/ENVIRONMENT.md -- tests/unit/check-env-doc-sync.test.ts flags any env var read in code but missing from both doc files. Documented it (both required -- the same test enforces the pairing). 2. truncateForLog()'s summary only computed messageCount from obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses request (which uses input[], not messages[]) got summarized with no count at all, leaving the dashboard's "Full Conversation" panel nothing to base its "N messages not shown" placeholder on for any Responses-API conversation, even though the same summarization logic applies to it. Test plan: - TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test ("captures a message count for Responses API bodies too") confirmed failing against the pre-fix code, passing after. - tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no longer appears in codeMissingEnv (remaining drift in that test is pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS, COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS, TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine upstream/release/v3.8.50 checkout, base-red inherited: #9985). - tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing. - npx tsc --noEmit / npm run lint -- clean. ⚠️ base-red inherited: #9985 * docs(logging): consolidate CHAT_LOG_MAX_BODY_KB into a single entry per file The variable was already documented (with a stale src/lib/chatLogTruncation.ts reference in .env.example); keep the new richer entries next to the CHAT_LOG_* family and drop the old duplicates. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
d259d9fcba |
fix(ci): clear base-reds on release/v3.8.50 (round 3) (#10213)
* fix(ci): clear base-reds on release/v3.8.50 (round 3) - CHANGELOG.md: restore the top [Unreleased] section dropped by the #10189 reconcile (docs-sync gate: first section must be Unreleased) - env-doc-sync: document CONDUCTOR_ORCHESTRATOR_TOKEN + CONDUCTOR_SPOKESPERSON_URL in .env.example/ENVIRONMENT.md; allowlist the CI-only GITHUB_STEP_SUMMARY and TS7_BASE_REF (ts7 ratchet signals); drop a stray merge artifact line - providers: restore the audited chatanywhere metadata entry that base-reds round 2 dropped together with its duplicate — the provider was half-wired (registry+endpoint without APIKEY metadata), which is what the wave3 test catches; re-pin providers-constants-split at the measured 228 - docs counts: 338 -> 339 (today's +2 void-ai/helixmind, -1 Puter) via gen:provider-reference + README/AGENTS/llm.txt/package.json/diagrams/i18n mirrors - file-size ratchet: annotated rebaseline for the two pre-existing drifts (ModelSelectModal 1138, gateways 1250) following the 2026-08-11 precedent Refs #9985 * fix(ci): base-reds round 3b — stale sibling tests + mode-pack weight contract - check-docs-counts-sync.test.ts: drop the imports/subtests of the four helpers #10196 removed from the gate script (readMcpFactsFromSource, listLocalizedDocs, makeRequiredCountsValidator, checkFreeTierInventory) — the new-API tests that #10196 added stay; the file now loads again under the node runner - quota-connection-recovery.test.ts: convert from vitest APIs to node:test — the file lives in tests/unit/*.test.ts (node-runner glob) and the vitest runtime crashes when imported outside vitest, killing the whole shard entry - modePacks.ts: re-normalize all six mode packs to sum 1.0 — #8940 added sessionAvailability: 0.05 to every pack without rebalancing (1.05 total); ratios preserved exactly (÷1.05), so post-normalizeScoringWeights behavior is unchanged; restores the declared sum-to-1.0 contract the 4235 test pins Refs #9985 * fix(ci): base-reds round 3c — vitest siblings, weights default, secrets FP, mutation tap - DistributeProxiesButton.test.tsx: wrap renders in NextIntlClientProvider — #9245 localized the component (useTranslations) and left the test without the intl context, failing all 14 cases - scoring.ts: re-normalize DEFAULT_WEIGHTS to sum 1.0 (same #8940 class as the mode packs — sessionAvailability added without rebalancing; ratios preserved) - .gitleaks.toml: generalize the kimi sponsor-banner localStorage-key allowlist to -v\d+ — #10200 bumped v1→v2 and the stale regex regressed the secrets ratchet with a false positive - stryker.conf.json: register 6 covering unit tests in tap.testFiles (4 modules) so their mutant kills count — unblocks check:mutation-test-coverage --strict Refs #9985 * fix(ci): base-reds round 3d — inspector factor gap, stale registry/gap tests, i18n key sync - comboScoringInspector: add cacheAffinity/sessionAvailability/connectionDensity to FACTOR_KEYS + the factor-key type — calculateScore() weighs them but the breakdown omitted them, so the explained contributions never summed to the reported score (inspector bug, red on the pure tip) - combo-scoring-inspector.test: make the explicit-weights override sum-neutral (±0.05 shift) so it stays valid for any DEFAULT_WEIGHTS values — the hardcoded override only summed to 1.0 against the pre-#8940 defaults, which is also why explicit weights silently fell back to 'default' on the tip - unorouter-registry.test: align to the canonical .com host (api.unorouter.ai 301-redirects there, verified live) and to wave4's live model discovery (passthrough, no static seed) — the .ai/auto-model expectations were stale - check-migration-numbering.test: 147 left KNOWN_GAPS when 147_api_keys_model_access_mode.sql landed — assert absent (same as 143) - i18n: sync-ui pass — 35,914 missing UI keys stamped as __MISSING__ placeholders across 42 locales (mechanical; greens the pt-BR key-presence integrity test; coverage pct unchanged by design — translation is a separate workstream) Refs #9985 * fix(ci): base-reds round 3e — 2 real defects + 14 stale sibling tests (waves A-E) Real defects fixed: - src/lib/db/apiKeys.ts: #9313's empty-allowlist early return bypassed the group permission check, silently disabling group deny rules (#8817) for every key without a per-key allowlist; fall-through restored, restricted+[] deny-all kept - open-sse/utils/proxyFetch.ts: #10032 re-appended the raw transport error to the propagated message, reintroducing the proxy user:password leak #9837 closed; new redactProxyDetailsInMessage() keeps the reason, redacts URL/credentials - .github/workflows/quality.yml: #10134 added the TS7 ratchet as a separate blocking step AFTER the aggregated gates — the exact #8542 masking mechanism; folded into the non-fail-fast loop (still blocking, still PR-only) ⚠️ CI edit, gate-strengthening — explicit owner sign-off requested on the PR - src/i18n/messages/ko.json: 3 machine-mistranslation regressions caught by the #8244 glossary checker (장애인→비활성화됨, 양말5://→socks5://, 비클로드→Claude가 아닌) Stale sibling tests aligned to deliberately-moved contracts (each cites its mover): request-log-detail-layout + -stream (#9245 intl provider), repro-8542 pin update, quality-rail-gate-membership (#10134 shape), agentSkills-routes 45→46 (#9058), cloudflare-ai-catalog-8717 (#8804 supersedes #8808), executor-xai (#9994), vision-bridge-claude-wire (#9463 minimax→openai), sse-auth forced-pin (#8893), tls-proxy-context (strengthened leak guards), rate-limit-local-error-classification (#9164/#9342), minimax-thinking-signature (#9463), codebuddy-cn (#9723 +1 test), github-copilot-custom-model (#9050), providers-g4f-batch3 (#9584), synced-capability-warmup (#9199, stricter), sidebar-tools-group (#8221), oauth-modal-grok-cli-paste (#9245); agentSkills/catalog.ts comment 45→46; file-size rebaseline for proxyFetch (+19, annotated) Refs #9985 * fix(ci): base-reds round 3f — waves F-J: 9 more real defects + stale sibling sweep Real production defects fixed (all red on the pure tip, each with its origin): - routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login local-only pattern — the route spawns a browser, so the loopback gate for a process-spawning route was gone (Hard Rules #15/#17); restored (314 guard tests green) - agentSkills generator: #9058's category dispatch gave the config category an empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync; fixed + SKILL.md regenerated via the official generator - imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the fal cross-provider fix intact - imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai, violating the pinned 2026-07-31 operator decision (adobe-firefly owns them); fal entries made prefix-only (dispatch already re-prefixes) - mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198 deleted the superseded falHandler — tests were hitting the live network - bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the library defect at the root and re-aligned chat-rate-limit-body-lock to the working reservoir contract - processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the #9156 launchd bug) and dropped #9209's ipv4first args; both restored - openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on the schemaless JSON-string path; gate extended - i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's zero-marker policy re-established via the sanctioned translation backend Stale sibling tests aligned (movers cited inline): chat-helpers (#9447), executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759), web-session-credentials (#8974), production-build-module-integrity (positive anchor added), agentSkills-generator/skillManifestsLint/skills-injection/ agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010), model-catalog-policy-invalidation (#8906), model-alias-seed (#9485), reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert helper), oauth-google-loopback (43-locale resurrected-key removal) Validation: 501/501 across the 47 touched test files; typecheck:core, lint, file-size, docs-sync all green. Refs #9985 * fix(ci): base-reds round 3g — wave K/L: 4 more real defects + stale alignments Real defects: - base/reasoningEffort.ts: the stale duplicate cherry-pick #9612 re-added the codex minimal→low rewrite that #9883 had deliberately removed (OMP minimal passthrough); block removed again - cursorImages.ts: #9840 wired prepareCursorImageForWire (sharp re-encode, fail-closed) into the SHARED resolveCursorImages, breaking zai-web and conol-web image uploads (HTTP 400 'undecodable'); new prepareForWire opt-out, Cursor default path unchanged (8 cursor suites green) - modelCapabilities/snapshot: catalog prepare still issued 323 per-model reads of model_context_overrides + max_input_tokens overrides, violating #9199's bulk-load contract; both now resolve from the snapshot single pass - v1-models-discovery-conformance: re-pinned to the bounded 30s SWR window (#9199/#10198) — the old 'stale-first regardless of age' contract is gone Stale tests aligned (movers cited inline): codex-tools-strict-default (#9828 redundant-oneOf strip), devin-providers (#9245 i18n), db-migrationrunner- constants-split (147→151 renumber #8228), gitlab-duo-oauth-setup (#9245), chatcore-extracted-modules (#9161 outbound-protocol keying) compression-api CI failures were cascade artifacts of codex-tools-strict-default failing in the same force-exit shard process — no own defect (171/171 local). Refs #9985 * fix(test): compression-api — register both describes before the runner starts The DATA_DIR setup + route/db top-level awaits sat BETWEEN the two describes; under --test-force-exit (the CI unit-runner flag) the process exits once the already-registered tests finish, so on slow CI machines the whole second describe died as 'Promise resolution is still pending' — the recurring CI-only shard-2 failure that never reproduced locally without the flag. Moved to the top of the file; 10/10 under --test-force-exit locally. Refs #9985 * fix(quality): freeze modelCapabilities.ts at 1006 (annotated) — snapshot routing growth Refs #9985 * fix(quality): move the modelCapabilities freeze into the frozen map (nested schema) Refs #9985 * fix(i18n): translate all 39,718 pending UI keys across 42 locales (owner-approved) Mass-translated every __MISSING__ placeholder via the official i18n:sync-ui --translate-markers pipeline (operator backend), restoring i18nUiCoverage to the 100 baseline (was 89.9 after the merge-storm UI landings + the 42 keys #9038 never shipped). Post-pass repairs, all caught by the existing gates: - glossary: retired renderings the machine reintroduced normalized again (提供商→提供者 zh-CN/zh-TW, 鏈接→連結, 文檔→文件, 調用→呼叫, 供應商→提供者, 響應→回應, 不活躍→未啟用 zh-TW; 클로드→Claude, 옴니루트→OmniRoute ko); DATA_DIR forbidden rendering avoided via 数据文件夹 rephrase - ICU integrity: 120 values with renamed/dropped {params} repaired (39 positional renames, 81 reset to the en source — functional over fluent) Validation: glossary/pt-BR/vi/deno-relay/settings-keys/value-drift/google- loopback suites 76/76; placeholder diff en×42 locales = 0; worst-locale coverage = 100.0%. Refs #9985 --------- Co-authored-by: backryun <bakryun0718@proton.me> |
||
|
|
f1eb0b8357 |
refactor(providers): remove the Puter provider at its owner's request (#10210)
Remove the Puter provider (id `puter`, alias `pu`) entirely, at the request of Puter's owner, Nariman Jelveh: - registry entry (open-sse/config/providers/registry/puter/) and PuterExecutor (open-sse/executors/puter.ts), with their registrations - API-key preset card (gateways.ts), provider icon and public SVG asset - 33 free-model catalog entries (pool `puter`) - authHint i18n key across all 43 UI locales - credential-requirement frozen-list entry and related comments - docs: ARCHITECTURE, CODEBASE_DOCUMENTATION, FREE_TIERS (removal note), PROVIDER_REFERENCE regenerated (337 providers), translated doc mirrors, llm.txt + its 42 i18n mirrors, README/AGENTS/package.json counts (338→337 providers, 144→145 migrations) and the 5 canonical SVGs - migration 152 cleans up stored puter connections/keys/custom models; historical usage records are preserved (same principle as migration 151) - regression guard: tests/unit/puter-provider-removed.test.ts; puter fixtures in shared tests swapped for neutral providers; translate-path golden snapshot regenerated Historical CHANGELOG mentions are intentionally preserved; the removal carries its own CHANGELOG entry. Co-authored-by: backryun <bakryun0718@proton.me> |
||
|
|
f6ccd3cf9f |
fix(quality): green release/v3.8.50 base-reds round 2 (#9985) (#10131)
* fix(quality): green release/v3.8.50 base-reds round 2 — gateways/conol/deepai corruption, migrations, docs, ratchets, dashboard-typecheck Base-red fix for issue #9985 after the 2026-08-11 merge storm (99 PRs). Real defects fixed: - gateways.ts: close regolo entry (was swallowing naga-ac + chatanywhere from #9421), drop stale duplicate chatanywhere entry (#9594) - conol-web + deepai registry: correct ../shared import depth + deepai executor:default - modelSelectModalHelpers: close isProviderModelHidden (#9011) - driverFactory.test.ts: restore eaten test-closing brace (#9173) - usageTracking: remove duplicate cache_* props - modelCapability{Overrides,ResolutionSnapshot,Capabilities}: max_token -> max_output_tokens (#9199 vs #8908) + test align - videoGeneration: drop duplicate handleFalVideoGeneration import (mediaGeneration/fal canonical, #9982) - responseSanitizer: cast input_tokens_details before .cached_tokens access - EditConnectionModal: missing alibaba code fields, hoist validationPsd, providerPageHelpers Badge variant union - FreeBudgetCard: t() -> labels.noApiKey - peerRouting + cliRuntime: ProcessEnv typing - image-combo.test.ts: type any -> unknown - fal.test.ts: moved to tests/unit/services (collected path) 14 tests green - remove duplicate 143_job_registry.sql (146 canonical), KNOWN_GAPS fix Docs/ratchets (owner-authorized rebaselines, annotated): - CHANGELOG 3.8.50 living section restored + 42 i18n mirrors - MCP-SERVER.md 104->105 tools + i18n - ENVIRONMENT.md/.env.example: ADOBE_FIREFLY_CHROME_HEADED + DEBUG_CLAUDE_NONSTREAM - fabricated-docs allowlist: TELEGRAM proposal env vars - file-size: 5 grown files + proxyFetch 1207->1220 - dead-code 230->248, codeql 2->9 (drift from merged PRs, not this PR) - untrack _tasks symlink; agent-skills-sync --apply (config-codex-cli) * fix(changelog): reformat two feature fragments to the bullet convention (#9239, #9490) * fix(quality): prune stale ESLint suppressions (base-red) * fix(quality): resolve open-sse type errors + catalog/build regressions (base-red round 3) Storm-merge splices repaired in the base-fix PR #10131: - doctor.ts: AppConfig missing brokerSocketPath - conol-web.ts: Buffer not assignable to BodyInit (Uint8Array) - tinycms.ts: TinyCmsExecutor.execute return matches BaseExecutor (response/url/transformedBody) - tinycmsSigner.ts: encodeInto never-narrowing guard + dead wasm URL fallback (Turbopack) - virtualFactory.ts: options slot for resolutionSnapshot - bottleneckPatch.ts: insufficient-overlap casts (as unknown as) - imageCombo.ts: narrow handleImageGeneration union result - browser-worker.ts: AppConfig + turn.capabilities splice - conolDiscovery.ts: getProviderOutboundGuard from Policy module - catalog.ts: drop removed SWR hooks (getCatalogStaleWhileRevalidateMs + accessors), CatalogCachePolicy -> inline settings, resolve 4-arg call - catalogCache.ts: remove dead inFlight/promise refs - chat.ts: add isProviderBreakerFailureStatus import - model-catalog-cache-swr-8728.test.ts: align to #9199 new API (policy injection removed) * fix(quality): align UI test fixtures to current component contracts (base-red vitest) - setup-wizard: provide required serverState prop (component gained it in a merged PR) - grok-device-oauth-modal: next-intl stub resolves grok flow keys to EN labels - provider-quota-widget: label now inline (PR #8916 removed AutoRefreshButtonLabel extraction) — test the widget - use-provider-connections-cursor-refresh + phase1f: match /api/providers?provider=<id> query form; hoist heavy dynamic imports to module scope (timeout flake) - home-topology: mock next/navigation useRouter (component added node-click navigation) - cooling/lobe/AutoComboCatalog: raise cold-import describe timeouts to 30-60s - request-logger-*: align to current detail-view contract * fix(search): guard params.token undefined in serper headers (typecheck base-red) * fix(search): guard token headers + non-null providerConfig (typecheck base-red) * fix(changelog): restore base CHANGELOGs eaten by merge auto-resolve (43 files) --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: backryun <bakryun0718@proton.me> |
||
|
|
8bd17be8aa |
docs: refresh every stale count to measured values + harden docs-counts gate (#10196)
* docs(reference): regenerate PROVIDER_REFERENCE from live provider modules The catalog was hand-stale at 291 since 2026-08-05 while the live provider modules define 338 unique IDs. The generator also omitted the NOAUTH_PROVIDERS module entirely (10 providers) and hardcoded the executor count in its footer; both are now sourced from the live modules. Refs #9985 * docs: refresh stale counts across README/AGENTS/llm.txt and architecture docs Every count updated to values measured from the live code on 2026-08-12: providers 291/271/248/236/226/212->338, migrations 110/117/130->144, MCP tools 94/99/104->105 (base 42->43), scopes 13/32->31, strategies 17/18->19, Auto-Combo factors 12/13->14 (sessionAvailability row added to the table), executors 67/78/84/89->101, quality gates ~48->~80, locales 29/30/39/40+->43 (41 non-source), A2A skills 5->6 (list-capabilities), free tier 43 pools/516 models/~1.53B/~2.15B->42/495/~1.51B/~2.13B, contributors 500+->320+ (324 unique emails), llm.txt version 3.8.47->3.8.50. llm.txt i18n mirrors resynced (headers preserved, body mirrored). Refs #9985 * docs(diagrams): sync SVG hero/pillars/comparison/cli/tier numbers Text nodes and aria-labels only; layout, coordinates and animation values untouched. providers 278/290->338 (cli list footer 264->334 more), MCP tools 104->105, strategies 18->19, free tier 43 pools/460+/516 models->42/495, headline ~1.53B/~2.15B->~1.51B/~2.13B. All six SVGs re-validated as XML. Refs #9985 * feat(check): harden docs-counts gate - live provider source, llm.txt, migrations, SVGs The gate trusted PROVIDER_REFERENCE.md as the provider total, so a hand-stale doc (291 vs 338 live) kept it falsely green. New STRICT checks: doc total vs the live provider modules (same collections the generator unions), provider count in llm.txt and package.json description, migration count vs README/AGENTS/llm.txt, and a canonical-number sweep (providers/MCP tools/strategies/pools) over the six README SVG diagrams with coordinate/attribute-safe patterns. TDD: 9 new unit tests (red first on the missing exports, green after) in tests/unit/check-docs-counts-sync.test.ts. Refs #9985 * docs(readme): refresh What's New range and add v3.8.50 cycle highlights --------- Co-authored-by: backryun <bakryun0718@proton.me> |
||
|
|
a543992b96 |
docs: restructure — planning artifacts to _tasks, duplicate merges, nav gaps closed (#10190)
* docs: migrate planning artifacts out of docs/ (superpowers, sessions, proposals) * docs(ops): relocate redis/production and contribution guides, move ROADMAP to repo root * docs: merge duplicate guides (troubleshooting, chatgpt-web codex, docker channels, memory backend) * docs: close fumadocs nav gaps (meta.json manifests + index) * chore(check): drop dead allowlist entries after docs restructure * docs: prettier formatting follow-up on merged guides * docs: point README at merged guide targets and allow ROADMAP.md at repo root --------- Co-authored-by: backryun <bakryun0718@proton.me> |
||
|
|
acc066db3f |
[v3.8.50] feat(devin-desktop): replace public Windsurf provider (#8228)
* feat(devin-desktop): replace public Windsurf provider * fix(migrations): renumber Devin Desktop migration to 151 (avoid 147 collision) 147_windsurf_to_devin_desktop.sql collided with the released 147_api_keys_model_access_mode.sql — getMigrationFiles throws "Migration version collision detected" on every DB start. Base occupies slots up to 150, so renumber the new migration to 151 and point the windsurf→devin RENAMED_MIGRATION_COMPATIBILITY entries (and tests) at it. 147 is freed in KNOWN_GAPS since 147_api_keys now owns the slot. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
ce70df6003 |
feat(a2a): Conductor bridge — mirror OmniConductor hub tasks into the A2A TaskManager (PRD RF1) (#8080)
* fix(api): enforce model permissions on gateway mirrors (#9854) Co-authored-by: Xiangzhe <xiangzhedev@gmail.com> * cherry-pick(pr-9787): fix(sse): apply Azure param rules on azure-ai and clamp gpt-4o-mini output tokens (#9855) * fix(sse): apply Azure request-param rules on the azure-ai wire path Azure rejects several stock Chat Completions params on its newer deployments and returns HTTP 400 rather than ignoring them: max_tokens -> 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead. reasoning_effort -> Function tools with reasoning_effort are not supported. Those rules lived inline in AzureOpenAIExecutor, so they only covered the azure-openai provider. azure-ai (Azure AI Foundry) had no executor entry and fell through to the bare DefaultExecutor, so the SAME Azure deployment succeeded on one connection and 400'd on the other. Every agentic client sends tools on every turn, so azure-ai failed on the first request. Extract the rules to open-sse/executors/azureParamRules.ts, add an AzureAiExecutor that inherits DefaultExecutor's azure-ai URL/header/apiType handling unchanged and applies the shared rules, and register it for azure-ai. Also widen the deployment pattern to cover gpt-chat-latest: it is a moving alias that resolves to a GPT-5-era model and rejects max_tokens, but carries no version number for the token-boundary pattern to key on. Verified against the base regex - gpt-chat-latest did not match, which is exactly the observed 400. Regression guard: tests/unit/azure-param-rules.test.ts, including an assertion that getExecutor("azure-ai") no longer resolves to a bare DefaultExecutor. * fix(sse): clamp Azure gpt-4o-mini completion tokens to its 16384 ceiling Azure gpt-4o-mini deployments accept at most 16384 completion tokens and 400 on anything larger: max_tokens is too large: 32000. This model supports at most 16384 completion tokens, whereas you provided 32000. The 32000 is OmniRoute's own doing: adjustMaxTokens raises any smaller max_tokens to DEFAULT_MIN_TOKENS (32000) whenever tools are present, to avoid truncated tool arguments. That floor has no upper bound, so an agentic client asking for far less still trips the model ceiling on its first turn. Add scoped maxOutputCap rules in paramSupport.ts for both Azure wire paths. PROVIDER_MAX_TOKENS is the wrong lever here - it is provider-wide, and the same Azure resource also serves GPT-5 deployments with a much higher ceiling. Regression guard: tests/unit/azure-max-output-clamp.test.ts, which also pins that the clamp does not leak to gpt-5.1 or to gpt-4o-mini on other providers. --------- Co-authored-by: Mihaly Bodo <michael@proton-quantum.com> * maint: final follow-up cherry-pick #9783 (#9904) * fix(deps): bump transitive deps for 6 Dependabot + remaining audit vulns on main Same overrides as #9464 (ip-address, hono, fast-uri, socket.io-parser, undici) applied directly to main. Also covers brace-expansion (scoped), js-yaml v4 copies, and mermaid. npm audit: 6→0 vulnerabilities. Closes Dependabot #161-#166. * fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190) Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13 (with monaco-editor scoped override). Closes Dependabot #189, #190. Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge — awaiting Dependabot re-scan. npm audit → 0 vulnerabilities. * fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks) _tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential _tasks symlink can slip in via git add -A and, once pulled, checkout materializes it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks ignores the symlink too, preventing re-capture. * fix(translator): keep Responses namespace identity across the hub-and-spoke pivot Step 1 of the pivot (openai-responses -> openai) flattens namespace sub-tools to a qualified wire name (#8295) and records the `{namespace, name}` pair on a non-enumerable `_toolNameMap`. Step 2 (openai -> target) returns a brand-new object, so the property was dropped for every non-OpenAI target. chatCore then handed `null` to the #7936 response seam and namespace sub-tool calls reached the client under their flattened name, which Codex rejects with `unsupported call: <name>` — the symptom #7936 was opened to fix. Copying `_toolNameMap` through is not viable: openai-to-claude and openai-to-gemini publish their own `Map<string, string>` alias map on that same property during step 2, so it carries two incompatible types. This adds a dedicated `_namespaceToolIdentityMap`, propagated by translateRequest across the pivot; chatCore prefers it and falls back to `_toolNameMap` for the non-pivot producers. Both keys are stripped from the cliproxyapi wire body. Fixes #9780 * fix(chat): reduce file size Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(chat): reduce combined file size Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(chat): reduce combined file size Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: VXNCXNX <vincent@preuve.ai> * fix(sse): route claude/<provider>/<model> aliases for catalog-only providers (#9856) The /v1/models catalog mirrors `claude/<provider>/<model>` ids purely from the alias gate -- ccAliasPredicate.ts consults no provider registry. The request path additionally required the prefix to be an open-sse REGISTRY entry or an operator-defined custom node. Enterprise-cloud providers such as azure-ai / azure-openai live only in the provider catalog (src/shared/constants/providers/apikey/enterprise-cloud.ts). They route fine directly -- `azure-ai/Phi-4` returns 200 -- but have no open-sse registry entry, so the two sides disagreed: the catalog advertised `claude/azure-ai/<model>` while stripCcDiscoveryAlias refused to strip it. The unstripped id then fell through to normal resolution, which splits on the first / and parsed `claude` as the provider. Every Claude Code request for an Azure model was routed to the Claude provider instead: ROUTING: Provider: claude, Model: azure-ai/DeepSeek-V4-Flash Extract the predicate as `isRoutableProviderPrefix()` and widen it to the provider catalog (id + alias) alongside the open-sse registry, so the request path recognises exactly what the catalog can advertise. Regression guard: tests/unit/cc-discovery-alias-routable-prefix.test.ts pins azure-ai/azure-openai/azure as routable, keeps openai/anthropic routable, and keeps an unknown prefix non-routable. Verified failing before the widening. Co-authored-by: Mihaly Bodo <michael@proton-quantum.com> * fix(i18n): translate validation model keys in 34 locales (#9857) The provider-connection dialog (AddApiKeyModal / EditConnectionModal) rendered humanized key names instead of real copy for providers.validationModelId{Label,Placeholder,Hint} in 34 of 43 locales — the values read "Validation Model Id Label", "Validation Model Id Placeholder" and "Validation Model Id Hint" verbatim. Each translation follows the terminology and register already used by the neighbouring provider keys in its own file — e.g. de Anbieter/API-Schlüssel with formal Sie, fr fournisseur/clé API, ru провайдер/ключ API — and each locale's own "e.g." convention (z. B., 例:, напр., ör., cth., hal.). Source of truth is en.json, which labels the field "Validation Model" (no "ID"); a few older locales say "validation model ID" and were left untouched rather than propagating that divergence. Co-authored-by: Mihaly Bodo <michael@proton-quantum.com> * cherry-pick(pr-9770): chore(repo): ignore Electron build output unpacked into repo root (#9858) * chore(repo): ignore Electron build output unpacked into repo root electron-builder (squirrel-windows target) unpacks the packaged app -- the entire Chromium runtime, ~24k files -- directly into the repository root: OmniRoute.exe, chrome_*.pak, *.dll, locales/, resources/, icudtl.dat, snapshot blobs and the Chromium license files. None of it was covered by .gitignore, so `git add -A` would commit the whole runtime. Every rule is root-anchored (leading `/`) because a bare `locales/` or `resources/` would also swallow tracked sources -- notably the CLI translations in bin/cli/locales/*.json. Verified with `git check-ignore`: all artifact paths ignored, and bin/cli/locales/{en,de}.json remain tracked. * chore(electron): sync package-lock for windows installer deps Adds the lockfile entries for the Windows installer/signing toolchain that the electron build now pulls in: electron-builder-squirrel-windows, electron-winstaller and @electron/windows-sign (plus their transitive fs-extra/jsonfile/universalify/mkdirp pins), and bumps app-builder-lib and builder-util-runtime. Lockfile-only change; no source or runtime behaviour is affected. --------- Co-authored-by: Mihaly Bodo <michael@proton-quantum.com> * fix(skills): normalize web fetch credentials (#9859) Co-authored-by: backryun <bakryun0718@proton.me> * fix(types): narrow DeepSeek tool calls (#9860) Co-authored-by: backryun <bakryun0718@proton.me> * fix(perf): memoize synced pricing reads (#9861) Co-authored-by: chloeassistant <279834366+chloeassistant@users.noreply.github.com> * cherry-pick(pr-9744): test(integration): add general live-test tool for the real "default" combo + rootless wire capture (#9862) * test(integration): add general live-test tool for the real "default" combo Temporary WIP commit on this deferred branch — lands in its own separate PR once the bug-fix extraction batch is done (never bundled into a bug-fix PR). Unlike liveGeminiShared.ts (provisions its own narrow 2-model Gemini-only combo), this reads the REAL "default" combo currently configured on the target instance directly from the DB and exercises every provider/model step in it directly, bypassing combo routing, so live-test coverage always matches whatever is actually configured instead of a hardcoded snapshot. Live-verified against omniroute-beta (seeded with the real 18-model, 5-provider default combo): 14/18 models pass consistently across non-streaming + streaming Chat Completions and streaming Responses API. The 4 consistent failures are real external state (cerebras credits_exhausted, one deprecated openrouter free-tier model), not code regressions. (cherry picked from commit c40b13a48fd897259c56f5122e9e57a3dc7654ba) * test(integration): add rootless wire-capture correlation to the live-test tool Temporary WIP commit on this deferred branch — lands in the same final live-test-tool PR as the general default-combo suite, never bundled into a bug-fix PR. liveContainerHarness.ts spins up a dedicated, throwaway podman container (same runner-base image target as the operator's local dev/beta containers) so wire-capture tests are fully self-contained: builds the image if missing, starts the container with a persistent data dir, waits for health, seeds the real "default" combo + provider connections from the operator's local omniroute-dev instance (idempotent — only runs once per data dir), and provisions API keys via the running instance's own auth flow. wireCapture.ts captures the container's actual network traffic via `podman unshare nsenter --net=<container netns> -- tcpdump` — no root needed, verified working live (this generalizes the root-requiring `sudo nsenter -t $PID` command scripts/sre/tcp-close-analyzer.py already documented for the same rootless-Podman netns problem; that script's docstring now documents both). Capture and analysis needed two real fixes found only by running the pipeline live: `-U` (unbuffered tcpdump writes) plus a `pkill -f <pcap path>` fallback, since `podman unshare -> nsenter -> tcpdump` is a 3-level subprocess chain and SIGTERM to the top-level process doesn't reach the tcpdump grandchild, leaving an orphaned process and a truncated/unreadable pcap; and filtering on the container's internal listening port (20128) rather than the dynamically-assigned host port, since capture happens inside the container's own network namespace where only the internal port is meaningful. live-default-combo-wire-capture.test.ts (gated on RUN_LIVE_WIRE_CAPTURE=1) ties it together: sends a small representative sample of requests through the real default combo, then cross-checks each one's app-level JSON status against the actual HTTP status line observed on the wire via scripts/sre/tcp-close-analyzer.py's stream reassembly — catching bugs where the app layer claims success but the wire shows a truncated/reset stream, not just what liveDefaultComboShared.ts's existing breadth suite already covers. Live-verified end-to-end: 4/4 sampled requests correlated correctly across 8 captured TCP streams, container + capture process fully torn down afterward (verified no orphaned podman container or tcpdump process left running). sendModelRequest/filterActiveModelTargets (liveDefaultComboShared.ts) gain optional baseUrl/apiKey overrides, defaulting to the existing module-level omniroute-beta target, so the wire-capture suite can point the same request-sending logic at its own dedicated container instead. (cherry picked from commit 914a7e42cbe914f257db9f72eedc902ee1532083) --------- Co-authored-by: Markus Hartung <mail@hartmark.se> * maint: follow-up cherry-pick fix-in-place #9741 (conflict-resolved fallback) (#9895) * fix(responses-api): sync reasoning-cache write index with the fixed read side The turn-index-hardcoding fix updated the reasoning-cache read side (translator/index.ts's main replay loop) to key lookups by the assistant message's real position in the messages array, but two other spots still used the old hardcoded convention: - chatCore.ts's write side (both the streaming and non-streaming completion paths) still cached every response under a hardcoded messageIndex: 0. - translator/index.ts's own plain-turn (non-tool-call) cache-key lookup ALSO still hardcoded messageIndex 0 at its call site — a second, previously undiscovered instance of the same class of bug, found while re-verifying this fix against the current upstream tip (the original fix only addressed the write side). Past the first assistant turn these conventions no longer matched, so DeepSeek/Xiaomi-mimo plain-turn reasoning replay silently missed the cache and fell back to the placeholder (or, once #9573 removed the placeholder fallback, to an absent field) in ordinary multi-turn conversations. Compute the write-side index from the incoming request's message count instead, and use the real loop-provided messageIndex on the read-side lookup, both matching the position the response occupies once the client appends it to history for the next turn. Note: this was originally part of a larger squashed fix (output_index collision prevention across reasoning/message/tool_call items, reasoning-content-alias generalization) that has since been superseded by upstream's own independent fix — translator/response/openai-responses.ts now has its own dense-output-index-sort + getReadableReasoningValue implementation (own comment: "mirrors upstream PR #721"). Only this narrower, still-genuinely-broken write/read index sync survives as a distinct bug. Test plan: - TDD: tests/unit/reasoning-cache.test.ts's new end-to-end "write side (chatCore's messageIndex) and read side (translateRequest) agree on the same key end-to-end" test, plus the pre-existing "should inject placeholder for a plain (non-tool-call) DeepSeek turn" and "should replay cached reasoning for a plain (non-tool-call) DeepSeek turn when available" tests — confirmed failing against the pre-fix code on a clean release/v3.8.50 checkout (both the hardcoded-0 write side AND the hardcoded-0 read-side lookup independently reproduce the mismatch), passing after both fixes - npm run typecheck:core — clean - npm run lint — clean - npm run check:file-size — clean (chatCore.ts rebaselined 5034->5042 for the messageIndex computation at both call sites; reasoning-cache.test.ts frozen at 1035, matching the original fix's own rebaseline) - 2 pre-existing, unrelated test failures in the same file ("should replace empty-string reasoning_content with NON_ANTHROPIC_THINKING_PLACEHOLDER on cache miss", "should inject placeholder for a plain (non-tool-call) DeepSeek turn missing reasoning_content") confirmed present on a completely clean, untouched release/v3.8.50 checkout — these test obsolete placeholder-injection behavior the code deliberately removed per #9573 (see the code's own comment); not touched by this PR * fix(chat): reduce file size Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(chat): reconcile file-size baseline Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Markus Hartung <mail@hartmark.se> * cherry-pick(pr-9738): feat(logging): make the chat-log truncation limit configurable, bumped default 128x (#9863) * feat(logging): make the chat-log truncation limit configurable, bumped default 128x The 8KB cap on logged request/response bodies (open-sse/handlers/chatCore/logTruncation.ts::truncateForLog()) was hardcoded — trivially exceeded by any real multi-turn agentic conversation, meaning the dashboard's "Full Conversation" panel could only ever show a placeholder instead of the actual messages for nearly every logged row of any conversation with real substance. - Added CHAT_LOG_MAX_BODY_KB env var (src/lib/logEnv.ts:: getChatLogMaxBodyBytes()), default 1024 KB (1MB) — a 128x bump from the old hardcoded 8KB — following the same configurable-limit pattern as the sibling CHAT_LOG_TEXT_LIMIT/CHAT_LOG_ARRAY_TAIL_ITEMS/etc. vars. - Documented in .env.example and docs/reference/ENVIRONMENT.md. estimateSizeFast() (open-sse/utils/estimateSize.ts) has been substantially rewritten upstream since this bug was first found (now an iterative Frame-based walker with a separate node-visit budget, not the simple stack loop originally patched) — re-implemented the fix against the current algorithm rather than porting the old diff: the byte early-exit was unconditionally the module-level ESTIMATE_SIZE_BYTE_LIMIT (256 KiB) with no way for a caller to raise it, so any caller comparing against a bigger configured threshold could never see a size above ~256 KiB — every payload between 256 KiB and the caller's real limit looked "under threshold" and truncation never fired, the opposite of intended. Added an optional byteLimit parameter (default unchanged at ESTIMATE_SIZE_BYTE_LIMIT, so isSmallEnoughForSemanticCache's existing behavior is untouched) threaded through both the byte-check early-exit and the node-budget-exhaustion fail-closed fallback, with truncateForLog() now passing its own configured getChatLogMaxBodyBytes() value through. * feat(dashboard): show conversation session tag in request detail metadata Adds a "Conversation" field to the request detail panel's metadata grid (after "Combo"), showing the request's conversation id (sessionTag) for quick reference/copy. --------- Co-authored-by: Markus Hartung <mail@hartmark.se> * cherry-pick(pr-9735): feat(logging): bump CHAT_LOG_ARRAY_TAIL_ITEMS default 24 -> 128 (#9864) * feat(logging): bump CHAT_LOG_ARRAY_TAIL_ITEMS default 24 -> 128 Real agentic CLIs with many MCP servers routinely declare 40-50+ tools in a single request — a live OpenClaw session logged 47. The tail-24 default silently dropped the array's earlier entries behind an _omniroute_truncated_array marker, so investigating why a specific tool call (apply_patch) behaved oddly turned up nothing: its declared shape (function vs custom type) was unrecoverable from the call log across 40 recent requests, even though the calls themselves succeeded. Bumped the configurable default to comfortably cover real large tool lists with headroom. Updated .env.example and docs/reference/ ENVIRONMENT.md to match (env-doc-sync check passes). * test(logging): pin CHAT_LOG_ARRAY_TAIL_ITEMS default at 128 The bump commit had no dedicated test asserting the literal default value; the existing chatcore-log-truncation.test.ts derives its expectations from getChatLogArrayTailItems() itself, so it can't discriminate a regression back toward the old, too-small 24 default. --------- Co-authored-by: Markus Hartung <mail@hartmark.se> * fix(logging): use configurable max-depth when bounding logged tool_calls (#9865) requestLogger.ts's cloneBoundedForLog had its own hardcoded depth cap of 6, independent of the existing configurable getChatLogMaxDepth(). A typical Chat Completions response body's responseBody.choices[0].message.tool_calls[0].function sits at exactly depth 6, so every logged tool call's function field (name+arguments) was silently replaced with the literal string "[MaxDepth]" before ever being stored — corrupting the data, not just how it renders. Bumped the shared default 6->20 and switched requestLogger.ts to read it instead of using its own literal. (cherry picked from commit |
||
|
|
2b2d947faf |
fix(cache): add latency marker + per-key bypass for semantic cache (#8984)
* fix(cache): add latency marker + per-key bypass for semantic cache
Semantic cache silently corrupts latency measurements: a 10s upstream
call served from cache looks like 19ms. Three fixes:
A. Latency marker: cache HIT responses now carry
X-OmniRoute-Cache-Latency: synthetic so measurement tools can
distinguish real vs cached latency.
B. Per-key bypass: new apiKeys.cacheDefaultMode ('legacy' | 'bypass')
lets latency-sensitive clients opt out of cache reads entirely.
- DB column + migration (134)
- rowParser parseCacheDefaultMode
- API create default + PATCH update
- checkSemanticCache returns null on bypass
C. Type safety: ApiKeyRow/ApiKeyView/params updated, superRefine
guard includes cacheDefaultMode.
Cache write path intentionally unchanged: apiKeyId is already in the
cache signature (semanticCache.ts:140), so per-key isolation prevents
cross-key pollution.
Changed test files:
- tests/unit/chatcore-semantic-cache.test.ts (3 new tests)
Signed-off-by: Minxi Hou <houminxi@gmail.com>
* docs: document semantic cache latency impact + bypass configuration
---------
Signed-off-by: Minxi Hou <houminxi@gmail.com>
|
||
|
|
a99c795a67 |
Add native ChatGPT Web provider for Codex clients (#8949)
* Bypass proxy compaction for native Codex context
* Add native ChatGPT Web provider pipeline
* Add managed browser and tunnel deployment
* Add ChatGPT Web setup and doctor UI
* Document and test ChatGPT Web integration
* fix(security): register chatgpt-web-codex-doctor in LOCAL_ONLY_API_PATTERNS
The diagnostic route under /api/providers/{id}/chatgpt-web-codex-doctor
was not registered in the spawn-capable route guard. Adding it for
parity with the existing /login pattern.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(providers): route chatgpt-web-codex admin routes through a service boundary
The provider CRUD/doctor routes imported chatgpt-web-codex helpers
(finalizeValidatedChatGptWebCodexSecrets, encode/decodeChatGptWebCodexSecrets,
getChatGptWebCodexDoctorStatus) directly from open-sse/executors/**, which
no-restricted-imports (EXECUTOR_IMPORT_RESTRICTION) forbids for src/app/**
files — executor implementations must stay behind an open-sse handler or
service boundary.
Add open-sse/services/chatgptWebCodexAdmin.ts as a thin re-export boundary
(mirroring the existing tokenRefresh.ts re-export pattern) and import from
there instead. No behavior change.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
||
|
|
45b997765c |
Document default behavior for ToS-flagged free-tier providers (addresses #10004) (#10013)
Co-authored-by: yulinlina <yulinlina@users.noreply.github.com> |
||
|
|
668ed4813f | refactor(providers): remove retired GitHub Models (#9023) | ||
|
|
5257d02447 |
fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in (#9549)
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login
POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by
provider slug — so browser login never launched for web-cookie providers.
Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears
on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated
Playwright interceptor and persist credentials with camelCase keys that
updateProviderConnection actually reads.
* fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in
Playwright is not available inside the pkg-packaged VibeProxyServices.exe,
so import('playwright') always failed with 'Playwright not installed' and
never opened a window. Launch Chrome/Edge with --remote-debugging-port and
capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead.
* fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load)
Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a
real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408
system under load while credits still work.
- Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback
- Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and
space-joined JWT+ARP (PasswordBox newline collapse)
- Reuse one ARP for storage upload + generate-async
- Clearer 408 errors when browser ARP is missing vs stale
- Unit suite 42/42
* fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive
Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of
ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo
HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep
Playwright warm-up opt-in only (headless Forter is rejected).
Also expand synthetic ARP shape with bfp/fpjs to match live successful captures.
* fix(adobe-firefly): renew sessions through durable CDP
* fix(adobe-firefly): isolate browser sessions per account
* fix(adobe-firefly): make account login fresh and deterministic
* docs(adobe-firefly): document renewal controls
* fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in
Stop colligo 408 thrash from stale Forter and frozen Google login during
Sign in with browser:
- CDP warm: clear Firefly origin storage + risk cookies (keep SSO); require
forter age under 10 minutes on loop and timeout paths; dual CDP queues;
await Runtime.runIfWaitingForDebugger; profile-lock launch retries
- Session: connectionId fingerprint; write-back JWT+Cookie; warm-fail
cooldown; fail closed risk_session_stale when forter is known-stale
- Client: submit gate around generate-async; max 2 attempts when forter
known-stale; poll 401 one refresh; pass sessionBrowserKey through handlers
- Login route: pure system Chrome/Edge CDP only; camelCase credential persist
- Unit: browser-login + firefly suites green (60)
* fix(adobe-firefly): dedupe CDP session hardening blocks after rebase
Remove duplicated guard blocks and test bodies introduced when rebasing
the CDP session hardening work onto release/v3.8.50, which already
carries the hardened implementation.
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
||
|
|
21fd0a94f8 |
feat(alibaba): free-tier routing with live quota sync (#8893)
* feat(alibaba): add free-tier routing with console quota and builtin allowlist Classify DashScope free vs paid models via console quota API, a hardcoded operator allowlist fallback, and per-connection drained tracking. Wire wildcard combo expansion, model refresh, combo exhaustion, and audit redaction for Alibaba console credentials. * fix(routing): reset forced connection pin and persist Alibaba free-tier drain Drop session affinity pins when a forced connection is excluded after 429, and record Alibaba free-tier exhaustion on upstream 403 so per-key drained lists stay accurate without blocking sibling keys. * fix(alibaba): prefer live quota sync over static free-tier allowlist Stop unioning the builtin text allowlist when a console quota snapshot exists, treat expired quotaValidityPeriod as not_capable, and add a dated JSON pack plus sync-alibaba-allowlist script for operator refresh without code edits. * docs(alibaba): document free-tier console path + allowlist env overrides Adds the 4 ALIBABA_FREE_TIER_*_FE_PATH / ALIBABA_FREE_TIER_ALLOWLIST_PATH env vars (referenced by alibabaFreeTierQuotaFetcher.ts and alibabaFreeTierAllowlist.ts) to .env.example and docs/reference/ENVIRONMENT.md so the env/docs contract check passes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(open-sse): split alibabaFreeTierQuotaFetcher.ts under file-size cap Extract pure parsing/classification/eligibility-filtering logic into alibabaFreeTierQuotaClassify.ts and shared types/primitives into alibabaFreeTierQuotaTypes.ts, leaving the HTTP/console-fetch flow in the original file. Public API is unchanged (re-exported), behavior is identical. Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com> * fix: resolve typecheck errors in alibaba-free-tier routing --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com> Co-authored-by: AndrianBalanescu <andrian@balanescu.dev> |
||
|
|
0135815c31 |
[v3.8.50] fix(adobe-firefly): durable session, Chrome recovery, browser sign-in (#8578)
* fix(adobe-firefly): durable session, off-screen Chrome recovery, browser sign-in
Rebuild x-arp-session-id from Cookie pieces (sid/ark/forter) so aux_sid is never
sent as ARP. Sticky ARP + submit spacing reduce mid-batch colligo 408 thrash.
Add optional managed Chrome warm (off-screen headed by default; Forter rejects
headless) and POST /api/providers/{id}/login browser sign-in that returns JWT+Cookie
after a fresh SSO. Visible sign-in resets off-screen window placement and clears
prior Adobe session when adding another account.
* fix(adobe-firefly): cast Node Buffer to ArrayBuffer and harden chrome runtime null close
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
* fix(docs): sync docs-counts gate and env var contract for adobe-firefly
Update executor/OAuth-provider counts in ARCHITECTURE.md and
CODEBASE_DOCUMENTATION.md to match the real code (89 executors, 21
OAuth providers), and document the Adobe Firefly Chrome-driven
session-refresh env vars in .env.example and ENVIRONMENT.md so the
env/docs contract tests pass.
Co-authored-by: artickc <artickc@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
|
||
|
|
d3037d1fdc |
feat(resilience): abort persistently slow upstream streams (#9709) (#10012)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
344e4398c8 |
fix(quality): green release/v3.8.50 base-reds — env-doc sync + file-size freeze (#9985) (#10032)
* fix(quality): green release/v3.8.50 base-reds — env-doc sync + file-size freeze (#9985) Sweep base-reds from issue #9985 on release/v3.8.50: - env-doc-sync: add COMMANDCODE_API_URL + ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS to .env.example and ENVIRONMENT.md (in code, missing from docs); add OMNIROUTE_STRICT_SYSTEM_PROVIDERS + TLS_FINGERPRINT_PROVIDERS to ENVIRONMENT.md (in .env.example, missing from doc). Restores the 3-way env contract. - file-size: freeze open-sse/utils/proxyFetch.ts at 1207 (new proxied-TLS fetch helper over the 1000 cap). Owner-authorized quick rebaseline; slim for v3.9.0. Co-authored-by: OmniRoute maintenance <maintainers@omniroute.local> * fix(quality): green open-sse+dashboard typecheck base-reds (#9985) Release-equivalent fast-gates surface 5 real TS regressions inherited by the base from merged Fal/guardrails/cursor work (fast-gates PR->release do not run these, so they accrued on release/v3.8.50): - open-sse/handlers/imageGeneration/providers/fal.ts: normalizeProviderImagePayload missing 4th 'b64_json' arg (TS2554). - open-sse/handlers/videoGeneration/falHandler.ts: narrow video to Record before .url. - src/app/api/v1/images/generations/route.ts: type the toJsonErrorPayload read. - src/lib/guardrails/visionBridgeHelpers.ts: cast through unknown for UA fetch. - src/lib/providers/mergeProviderModelListing.ts: drop index-signature requirement that made interface RegistryModel[] unassignable (TS2322, from #9911). All fixed in source (keeps the gates meaningful); each reproduces on the base tip. Co-authored-by: OmniRoute maintenance <maintainers@omniroute.local> * fix(quality): allowlist onnxruntime-node in dependency allowlist (#9985) check:deps base-red — onnxruntime-node is a real production dep (transformers embedding path) landed via the LLMLingua/transformers bump (#9962) without an allowlist entry. Legit package: microsoft onnxruntime, verified in registry. * fix(quality): rebaseline CodeQL ratchet 1->2 for #9940 fingerprint alerts (#9985) Base-red: 2nd js/insufficient-password-hash alert on chatBodyAdmission API-key fingerprints (sha256->16-hex admission-lane key), not password verification. Reproduces on release/v3.8.50 tip. Owner-authorized rebaseline (revisit v3.9.0). * fix(quality): green release/v3.8.50 unit base-reds (#9985) 8 unit-test base-reds reproducing on the pristine release tip, fixed in-source (fast-gates PR->release do not run the unit suite, so these accrued silently): - ServiceSupervisor: spawn-failure now resolves with error status (was throwing); health-probe-failure path still rejects. Distinct via spawnFailed flag. - stream + responseSanitizer: numeric passthrough id preserved as string (was regenerated chatcmpl-); finish chunk with empty delta no longer swallowed by the emptyChoices guard. - proxyFetch: genuine (non-abort) proxy transport failures keep the underlying reason in the surfaced error. - auto-combo builtinCatalog: advertised undefined-variant auto/* ids (auto/chat, auto/best-chat, auto/pro-chat) materialize instead of throwing 'Unknown'. - getTranslations en.json: add missing providers.iconUrlInvalid. - optional-transformers-dependency.test: reconcile to #9962's deliberate move of @huggingface/transformers to a regular dep (napi onnxruntime). Co-authored-by: OmniRoute maintenance <maintainers@omniroute.local> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@gmail.com> Co-authored-by: OmniRoute maintenance <maintainers@omniroute.local> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
8d78e3dfd3 |
fix: per-connection virtual admission lanes (#9654) (#9940)
* fix: add per-connection virtual admission lanes (#9654) Worst-day-ever analysis to harden AdaptiveAdmissionController: - Guard expireEntry() against null entry (CRITICAL null deref) - Add deleteLane() to drain+reject on LRU eviction (HIGH orphaned promises) - Fix Map mutation during evictIdleLanes iteration (MEDIUM safety) - Add ADMISSION_LANE_EVICTED reject code (MEDIUM clarity) - Pass sessionId to admitChatRequest in route.ts - virtualLanes defaults to false in validateConfig - 7 new controller tests + 14 new byte-level admission tests - Assertions tightened from >= to === (Matt Pocock methodology) Debunked 2 false positives: concurrency race (single-threaded JS) and memory amplification (FairCostQueue bounds per-lane). Fixes #9654 * fix(admission): restore bounded queue-wait on per-connection lanes (#9654) The per-connection lane refactor dropped the bounded queue-wait (acquireHeavyWithin / #waiters / queueMs). #9654's acceptance criteria and #9608 section C prefer server-side wait/pacing up to defaultMaxWaitMs over an instant retryable 503. - ChatAdmissionController: re-add #waiters FIFO + acquireHeavyWithin(timeoutMs); queueMs: 0 preserves the instant-503 path - admitChatStructure and admitChatRequest.reserve are async again and take queueMs - route: pass CHAT_ADMISSION_QUEUE_MAX_MS and await the admission calls - per-connection lane tests await the async admitChatStructure Admission suite: 114/114 pass (bun test, 7 files). * chore: re-trigger CI after dast-smoke infra cancellation (#9654) * feat(admission): cancel queue-wait on client abort (#9654) U2 from KC plan 2026-08-09-001. Thread the request AbortSignal through acquireHeavyWithin so a disconnected client stops parking in the FIFO for the full queueMs. - acquireHeavyWithin(timeoutMs, signal?): on abort the waiter is removed from the FIFO immediately and the promise resolves null early; pre-aborted signals never park; the deadline timer is cleared when abort/release wins the race - admitChatRequest reserve() passes request.signal; admitChatStructure gains options.signal; the route threads request.signal - 5 exact-assertion tests (settle-early, pre-aborted, byte-heavy, structural, FIFO-preservation): 119/119 across the 7-file suite * fix(admission): bound queued bytes for the queue-wait heap valve (#9654) U3 from KC plan 2026-08-09-001. The restored queue-wait parks fully-buffered bodies; without a cap, several large coding-agent bodies (~750 KB) waiting at once recreates the #4380 heap amplification this module was built to stop. - acquireHeavyWithin(timeoutMs, signal?, queuedBytes): each parked waiter is charged its buffered size against CHAT_ADMISSION_MAX_QUEUED_BYTES (default 4 MB); over-budget waits reject immediately with a retryable 503 and never park. The charge is released on wake, abort, or timeout. - Real sizes threaded from admitChatRequest (declared length / sniffed bytes); structural waits charge the conservative 256 KB weight. - Lower default OMNIROUTE_CHAT_ADMISSION_QUEUE_MS to 2000ms (was 5000ms). - Env vars documented in .env.example; 6 exact-assertion tests: 125/125 across the 7-file admission suite (was 119). * docs: map the two admission-lane systems for operators (#9654) U5 from KC plan 2026-08-09-001. Verifies lane metrics are exposed by the health payload (GET /api/monitoring/health -> adaptiveAdmission -> lane* fields) and records which lane system reports where: byte-level per-connection lanes (always on, memory scope) vs adaptive virtual lanes (opt-in via OMNIROUTE_CHAT_VIRTUAL_LANES, dispatch scope) plus the explicit opt-in ops note. * docs: add required frontmatter to admission-lanes doc (dast-smoke build fix) * docs: sync env vars with .env.example and ENVIRONMENT.md (docs gate fix) * fix(admission): complete REJECT_MAP, literal lane env read, split oversized test file Three CI-gate fixes surfaced by the post-merge check run (head |
||
|
|
792626c3c4 | Merge remote-tracking branch 'origin/release/v3.8.50' into codex/wave2b-9749 | ||
|
|
9f4186a2b2 |
Merge remote-tracking branch 'origin/release/v3.8.50' into merge-prs-9749-canonical
# Conflicts: # package.json |
||
|
|
754ba0fa86 | fix(release): repair post-sweep base regressions | ||
|
|
57fb90d734 |
maint: follow-up cherry-pick fix-in-place #9549 (conflict-resolved fallback) (#9881)
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login
POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but that service looks up the provider by
slug in TOKEN_EXTRACTION_CONFIGS. The lookup always missed and returned
"No extraction config" without launching a browser — so the VibeProxy
"Sign in" button for Adobe Firefly (and every other web-cookie provider)
never opened a browser.
Adobe Firefly additionally had no extraction config because its IMS JWT
is never in cookies/localStorage — it only rides on the Authorization:
Bearer header of firefly-3p.ff.adobe.io XHRs.
- Resolve the provider slug from the connection row and pass the slug
(not the DB id) to inAppLoginService.startLogin.
- Add open-sse/services/adobeFireflyBrowserLogin.ts: a Playwright
service that launches a visible browser at firefly.adobe.com and
intercepts firefly-3p requests to capture the IMS JWT + sherlockToken
cookie. Wire it into the /login route for the adobe-firefly slug.
- Fix latent bug: updateProviderConnection reads camelCase keys
(apiKey, providerSpecificData), so the previous snake_case call never
persisted extracted credentials.
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login
POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by
provider slug — so browser login never launched for web-cookie providers.
Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears
on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated
Playwright interceptor and persist credentials with camelCase keys that
updateProviderConnection actually reads.
* fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in
Playwright is not available inside the pkg-packaged VibeProxyServices.exe,
so import('playwright') always failed with 'Playwright not installed' and
never opened a window. Launch Chrome/Edge with --remote-debugging-port and
capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead.
* fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load)
Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a
real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408
system under load while credits still work.
- Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback
- Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and
space-joined JWT+ARP (PasswordBox newline collapse)
- Reuse one ARP for storage upload + generate-async
- Clearer 408 errors when browser ARP is missing vs stale
- Unit suite 42/42
* fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive
Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of
ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo
HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep
Playwright warm-up opt-in only (headless Forter is rejected).
Also expand synthetic ARP shape with bfp/fpjs to match live successful captures.
* fix(adobe-firefly): durable session, off-screen Chrome recovery, browser sign-in
Rebuild x-arp-session-id from Cookie pieces (sid/ark/forter) so aux_sid is never
sent as ARP. Sticky ARP + submit spacing reduce mid-batch colligo 408 thrash.
Add optional managed Chrome warm (off-screen headed by default; Forter rejects
headless) and POST /api/providers/{id}/login browser sign-in that returns JWT+Cookie
after a fresh SSO. Visible sign-in resets off-screen window placement and clears
prior Adobe session when adding another account.
* fix(adobe-firefly): renew sessions through durable CDP
* fix(adobe-firefly): isolate browser sessions per account
* fix(adobe-firefly): make account login fresh and deterministic
* chore(adobe-firefly): remove obsolete browser fallback
* docs(adobe-firefly): document renewal controls
* fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in
Stop colligo 408 thrash from stale Forter and frozen Google login during
Sign in with browser:
- CDP warm: clear Firefly origin storage + risk cookies (keep SSO); require
forter age under 10 minutes on loop and timeout paths; dual CDP queues;
await Runtime.runIfWaitingForDebugger; profile-lock launch retries
- Session: connectionId fingerprint; write-back JWT+Cookie; warm-fail
cooldown; fail closed risk_session_stale when forter is known-stale
- Client: submit gate around generate-async; max 2 attempts when forter
known-stale; poll 401 one refresh; pass sessionBrowserKey through handlers
- Login route: pure system Chrome/Edge CDP only; camelCase credential persist
- Unit: browser-login + firefly suites green (60)
---------
Co-authored-by: artickc <artur1992123@mail.ru>
|
||
|
|
5e5919dcc0 |
maint: follow-up cherry-pick fix-in-place #9631 (conflict-resolved fallback) (#9886)
* feat(db): add a job registry for scheduled background work Background jobs each ship their own timer today, so there is no list of what is scheduled, no history of what ran, and no way to pause one without an environment variable and a restart. The registry gives them one home: a jobs table holding the schedule, a job_runs table holding the outcomes, and a loopback-only API to inspect and control both. Cron jobs read their expression through an optional cronGetter rather than the stored column, so an operator changing OMNIROUTE_WARMUP_CRON does not need the row rewritten. register() is an idempotent upsert that refreshes the schedule but never overwrites `enabled` or `created_at`, which is what lets a job be re-registered on every boot without discarding the operator's toggle. Run history is pruned per job rather than globally, and safeRun records a failure for a handler that throws as well as one that returns success:false, so a crashing job leaves a trail instead of a gap. The API is under /api/jobs and gated to loopback in the route guard. It can trigger a run and flip a job off, which is runtime administration and does not belong on a remotely reachable surface. Signed-off-by: Minxi Hou <houminxi@gmail.com> * feat(jobs): move the budget reset and token health check onto the registry Both jobs owned their own timer and started themselves as an import side effect, so nothing could report whether they were running, when they last ran, or why a run failed. They now register with the job registry and are started from it, which also means their schedule and run history are visible through /api/jobs. startAll() runs each interval job's first tick synchronously, so both entry points start the registry only after initializeCloudSync() has been awaited. The old wiring reached that ordering two different ways: the budget reset was started after the init call, and the health check's first sweep sat behind a 10s timer. Replacing both with one startAll() would otherwise have moved the two handlers in front of the initialisation they run against. Both entry points also register the same pair of jobs. Registering one and not the other is how a background job goes missing without anything failing. sweep() now returns how many connections it swept, so the health check can record a real records_affected the way the budget reset does. The migration documents that column as a per-job count, and hardcoding zero would have left one of the two jobs reporting a number the schema promises but the code never produces. A skipped or empty sweep reports zero. Every existing caller ignores the return value. The token health check keeps its own disable semantics: the handler still calls isHealthCheckDisabled() before sweeping, so OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK, the production-build phase and the automated-test guard behave as before. Its registry adapter lives in src/lib/jobs/ next to the budget reset rather than in tokenHealthCheck.ts, which is already above its frozen size ceiling on the base branch and should not grow further. The adapter lets a failing sweep throw rather than reporting it itself, matching the budget reset: safeRun records a thrown error as a failure run with its message. The warmup job is seeded disabled. Its handler arrives with the warmup scheduler, and startAll() filters on enabled before it looks for a handler, so seeding it enabled here would warn about the missing handler on every boot. * fix: allowlist cron-parser dep and document OMNIROUTE_RUNNOW_TIMEOUT_MS env var Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
9fb7d6a493 |
cherry-pick(pr-9735): feat(logging): bump CHAT_LOG_ARRAY_TAIL_ITEMS default 24 -> 128 (#9864)
* feat(logging): bump CHAT_LOG_ARRAY_TAIL_ITEMS default 24 -> 128 Real agentic CLIs with many MCP servers routinely declare 40-50+ tools in a single request — a live OpenClaw session logged 47. The tail-24 default silently dropped the array's earlier entries behind an _omniroute_truncated_array marker, so investigating why a specific tool call (apply_patch) behaved oddly turned up nothing: its declared shape (function vs custom type) was unrecoverable from the call log across 40 recent requests, even though the calls themselves succeeded. Bumped the configurable default to comfortably cover real large tool lists with headroom. Updated .env.example and docs/reference/ ENVIRONMENT.md to match (env-doc-sync check passes). * test(logging): pin CHAT_LOG_ARRAY_TAIL_ITEMS default at 128 The bump commit had no dedicated test asserting the literal default value; the existing chatcore-log-truncation.test.ts derives its expectations from getChatLogArrayTailItems() itself, so it can't discriminate a regression back toward the old, too-small 24 default. --------- Co-authored-by: Markus Hartung <mail@hartmark.se> |
||
|
|
e117249baa |
cherry-pick(pr-9738): feat(logging): make the chat-log truncation limit configurable, bumped default 128x (#9863)
* feat(logging): make the chat-log truncation limit configurable, bumped default 128x The 8KB cap on logged request/response bodies (open-sse/handlers/chatCore/logTruncation.ts::truncateForLog()) was hardcoded — trivially exceeded by any real multi-turn agentic conversation, meaning the dashboard's "Full Conversation" panel could only ever show a placeholder instead of the actual messages for nearly every logged row of any conversation with real substance. - Added CHAT_LOG_MAX_BODY_KB env var (src/lib/logEnv.ts:: getChatLogMaxBodyBytes()), default 1024 KB (1MB) — a 128x bump from the old hardcoded 8KB — following the same configurable-limit pattern as the sibling CHAT_LOG_TEXT_LIMIT/CHAT_LOG_ARRAY_TAIL_ITEMS/etc. vars. - Documented in .env.example and docs/reference/ENVIRONMENT.md. estimateSizeFast() (open-sse/utils/estimateSize.ts) has been substantially rewritten upstream since this bug was first found (now an iterative Frame-based walker with a separate node-visit budget, not the simple stack loop originally patched) — re-implemented the fix against the current algorithm rather than porting the old diff: the byte early-exit was unconditionally the module-level ESTIMATE_SIZE_BYTE_LIMIT (256 KiB) with no way for a caller to raise it, so any caller comparing against a bigger configured threshold could never see a size above ~256 KiB — every payload between 256 KiB and the caller's real limit looked "under threshold" and truncation never fired, the opposite of intended. Added an optional byteLimit parameter (default unchanged at ESTIMATE_SIZE_BYTE_LIMIT, so isSmallEnoughForSemanticCache's existing behavior is untouched) threaded through both the byte-check early-exit and the node-budget-exhaustion fail-closed fallback, with truncateForLog() now passing its own configured getChatLogMaxBodyBytes() value through. * feat(dashboard): show conversation session tag in request detail metadata Adds a "Conversation" field to the request detail panel's metadata grid (after "Combo"), showing the request's conversation id (sessionTag) for quick reference/copy. --------- Co-authored-by: Markus Hartung <mail@hartmark.se> |
||
|
|
0bb17b91c6 |
maint: final follow-up cherry-pick #9812 (#9907)
* fix(deps): bump transitive deps for 6 Dependabot + remaining audit vulns on main Same overrides as #9464 (ip-address, hono, fast-uri, socket.io-parser, undici) applied directly to main. Also covers brace-expansion (scoped), js-yaml v4 copies, and mermaid. npm audit: 6→0 vulnerabilities. Closes Dependabot #161-#166. * fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190) Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13 (with monaco-editor scoped override). Closes Dependabot #189, #190. Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge — awaiting Dependabot re-scan. npm audit → 0 vulnerabilities. * fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks) _tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential _tasks symlink can slip in via git add -A and, once pulled, checkout materializes it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks ignores the symlink too, preventing re-capture. * feat(telegram): Mini App chat bridge — initData auth, update webhook, chat proxy Implements the Phase-1 slice of the Telegram Mini App integration (docs/proposals/TELEGRAM-MINIAPP.md): - src/lib/telegram/initData.ts — dependency-free WebApp initData HMAC-SHA256 verification (Telegram Bot API spec), with auth_date freshness check. - src/lib/telegram/config.ts — TELEGRAM_BOT_TOKEN / model / API base / timeout env config; token format validation; enabled gate. - src/lib/telegram/botApi.ts — minimal fetch-based Bot API client (sendMessage, editMessageText, setWebhook) + update shape helpers. - src/lib/telegram/chatProxy.ts — maps a Telegram user to a per-user OmniRoute API key (createApiKey, name telegram:<userId>) and proxies prompts through the existing handleChat pipeline. - src/app/api/telegram/update/route.ts — inbound endpoint serving both the Bot API update webhook (/start + chat replies) and the Mini App direct path (initData HMAC verified → 401 on mismatch). Public route prefix; own auth only. - src/app/miniapp/page.tsx — Telegram WebApp SDK chat UI. - Tests: telegram-init-data (7), telegram-botapi (5) — 12/12 pass. - Env docs: TELEGRAM_* vars in .env.example + ENVIRONMENT.md (sync ✓). - Route-validation check: PASS (body validated via Zod). --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: benzntech <bensonkbmca@gmail.com> |
||
|
|
247f2606cd |
fix(admission): queue heavyweight chat requests before 503 busy (#9845)
Agent clients (OpenCode, Claude Code, Cursor) fan out heavy sub-requests that land on the admission gate together. With the single heavyweight slot, concurrent heavy requests were rejected immediately with a retryable 503; clients burn their retry budget in seconds and the agent dies mid-task. Heavy requests now wait up to OMNIROUTE_CHAT_ADMISSION_QUEUE_MS (default 5000ms) for a slot before the 503, served FIFO; 0 restores the legacy immediate-reject behaviour. Applied to both the byte-based path (admitChatRequest) and the structure-based path (admitChatStructure, now async). Co-authored-by: herjarsa <herjarsa@users.noreply.github.com> |
||
|
|
71c85f31cd |
feat(guardrails): modality bridge core — vision mode/task-aware/cache/input_image + modalityBridge settings (#9759)
* feat(sse): unified media-part detection helper (image+audio, input_image) * refactor(guardrails): extractImageParts/comboStructure delegate to unified media detector * fix(sse): media detector — audio parts no longer shadow sibling/nested image indicators * fix(guardrails): close extract↔replace contract for input_image (allowlist + splice) * perf(guardrails): skip media traversal when bridge disabled; short-circuit combo image check * feat(guardrails): in-memory LRU bridge cache (sha256 keyed) * feat(settings): modalityBridge* schema with legacy visionBridge* fallback * feat(db): migrate visionBridge* settings to modalityBridge* (idempotent) * refactor(guardrails): harden bridge cache key/config + settings resolution (review minors) * feat(guardrails): vision bridge mode selector (auto/describe/reroute) short-circuit * feat(guardrails): task-aware vision description prompt (default on) * feat(guardrails): describe-path cache integration * docs(guardrails): review polish — cache-key coupling notes + helper header * feat(guardrails): in-memory bridge stats + modality-bridge response header * feat(api): modality bridge stats endpoint + header wiring in chat handler * docs(guardrails): document modality bridge mode/task-aware/cache/header + stats endpoint * chore: untrack _tasks symlink (inherited from base tip; blocks pre-commit tracked-artifacts gate) * fix(db): renumber modality bridge migration 139->140 (base renumbered ccr_blocks to 139) * docs(guardrails): migration filename touch-up 139->140 * docs(db): stale comment touch-ups after 139->140 renumber and #9688 landing * fix(db): renumber modality bridge migration 140->141 (base renumbered connection_runtime_state to 140) * test(db): migration test titles 139->141 --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
390690dd0a |
fix(logging): make stream-chunk capture and request-shape logging opt-in
Flip two heavy/noisy defaults to reduce resource load and log volume: - CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS now defaults to false. Stream chunks are the largest call-log artifact; capturing them on every request by default is what grows ~/.omniroute/call_logs by hundreds of MB in days. Operators can re-enable with =true. - OMNIROUTE_LOG_REQUEST_SHAPE now logs only when explicitly set to "1" (was: enabled unless set to "0"). Large-body diagnostics are debug tooling, not default behavior. Docs (.env.example + ENVIRONMENT.md) updated to match the new defaults. |
||
|
|
20a4ab6f55 |
fix(api): stop rejecting long chat histories by default (#9494)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). |
||
|
|
765fd71aea |
feat(lib): make MODELS_DEV_SYNC_ENABLED actually control the sync (#9483)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). |
||
|
|
217ac4c829 |
feat(warmup): proactive Claude warmup scheduler (#8848) (#9449)
Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). |
||
|
|
02534f4e8e |
feat(radar): contributor + supporter claim buttons on the activation screen (#9710)
* feat(radar): add F4/T7 contributor-claim / supporter-plans link config Pure, DB-free src/lib/radar/links.ts resolves the two outbound "get a supporter key" URLs (contributor GitHub-OAuth claim + supporter plans page), same env-override pattern as RADAR_FEED_URL. No pricing/value is ever resolved here (D14) — only the link. * feat(radar): relay F4/T7 claim/plans links via GET /api/radar/settings Smallest-surface option per spec: no dedicated route. The existing settings snapshot now also returns contributorClaimUrl/supporterPlansUrl so the dashboard client never reads process.env itself. Both are plain public URLs, gated by the same flag/auth checks as the rest of the response. * feat(radar): add contributor/supporter claim buttons to activation screen F4/T7 — "I'm a contributor" opens the GitHub OAuth claim flow; "Support the project" opens the plans/payment page. Both links come from the settings fetch (never a hardcoded URL in this client component) and open in a new tab. No price/value anywhere in the copy — the destination page is the only place pricing lives (D14). i18n: 5 new radarPage keys (claimSectionTitle, contributorButton, contributorHint, supporterButton, supporterHint) added to all 43 locale files with the English copy as fallback value. * docs(radar): document F4/T7 supporter-key acquisition paths RADAR.md: new "Getting a supporter key" section covering both claim flows, the two env-var overrides, and the current gap (no dedicated key-paste input in the dashboard yet — POST /api/radar/settings is the only way to set one today). ENVIRONMENT.md + .env.example: register RADAR_CONTRIBUTOR_CLAIM_URL / RADAR_SUPPORTER_PLANS_URL for check:env-doc-sync. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
1e15583f29 |
fix(radar): close audit gaps (auth, feed fields, opt-in state, sidebar gate, size cap) + daily sync scheduler (#9686)
* fix(radar): preserve extended feed fields and honor local enable override
applyFeed()'s MergedEntry shape omitted contextWindow/capabilities/limits/
setup even though FeedModel always carries them, so the dashboard's setup
link, Context column, and capability badges never rendered and the setup
page's provider lookup always failed. Both merge paths (mergeOne and
feedModelToMerged) now copy the four fields through, respecting rule 1
(local override wins) same as every other field.
feedModelToMerged() also unconditionally forced enabled:false when the feed
disabled a feed-only entry, even when the operator had locally overridden
enabled:true — mergeOne() already applies overrides after the disable rule
and got this right. feedModelToMerged() now only force-disables when there
is no local `enabled` override, matching mergeOne()'s semantics.
* fix(radar): cap feed sync response body at 10MB
syncRadar() buffered the entire feed response via
Buffer.from(await res.arrayBuffer()) with no size limit, so a
misconfigured or hostile RADAR_FEED_URL (or an upstream serving garbage)
could force an unbounded in-memory buffer. Enforcement is two-layered: a
Content-Length preflight skips reading an already-oversized body entirely,
and a running-total check while reading the stream enforces the cap even
when Content-Length is absent or understates the real size — concatenating
the accumulated chunks preserves the exact bytes the signature check needs.
Exceeding the cap returns a new { status: "too_large" } SyncStatus and
leaves the cache untouched, following the same non-destructive pattern as
every other sync failure (invalid_signature/invalid_schema/stale).
* fix(radar): gate the sidebar radar item behind RADAR_ENABLED
The "radar" sidebar item was registered unconditionally in
sidebarVisibility/sections.ts, but Sidebar.tsx has no feature-flag
awareness (it's a client component), so the link stayed visible and
clickable with RADAR_ENABLED off, landing on a 404 dashboard page.
Sidebar items gain an opt-in `featureFlagKey` field plus a pure
isSidebarItemVisibleForFlags() filter (fails open when a flag isn't in the
map, so a missing/not-yet-loaded key never hides an unrelated item). The
resolved flag value piggy-backs on the /api/settings response the sidebar
already fetches on mount (new `radarEnabled` field) rather than adding a
dedicated round trip.
* fix(radar): require auth on management routes, add GET settings
GET /api/radar/catalog, POST /api/radar/sync, and POST /api/radar/settings
had zero authentication — any client that could reach the local server
could read the merged catalog, trigger a sync, or flip the opt-in/set the
supporter key. All three (plus the new GET below) now call
isAuthenticated() from the shared apiAuth guard, same gate as the rest of
/api/settings/*. The RADAR_ENABLED flag-off 404 check keeps running FIRST
so flag-off inertia stays byte-identical (no auth prompt just to learn the
surface doesn't exist); auth runs after it, before any DB access.
Adds GET /api/radar/settings, returning { optIn, hasSupporterKey,
supporterKeyMasked } — the raw key never leaves the server on either verb.
The dashboard page's fetchSettings() now calls this endpoint instead of
inferring opt-in state from the catalog response (which always defaulted
to unknown/null), so an already-activated operator no longer sees the
activation screen on every reload. handleSync() also handles the new
too_large sync status introduced by the response-cap fix, reusing the
existing generic sync-failed copy (no new UI strings).
* docs(radar): fix stale feed URL, document tier header/auth/size cap
- RADAR_FEED_URL default was documented as radar.omniroute.dev in
ENVIRONMENT.md; the actual default (src/lib/radar/sync.ts) and every
other reference use radar.omniroute.online — fix the one stale spot.
- Correct the FREE_MODEL_BUDGETS source path: it's declared in
freeModelCatalog.data.ts, not freeModelCatalog.ts (which only
re-exports it).
- Document that the signed feed body's `tier` is always "live" (one
signed artifact per version) and the actually-served tier comes from
the `x-omniroute-feed-tier` response header, resolved with a Zod parse
+ fallback to the body field.
- Document that all four /api/radar/* routes now require auth
(isAuthenticated(), same gate as /api/settings/*), the new
GET /api/radar/settings route, and the new too_large sync status from
the 10MB response cap.
* feat(radar): daily sync scheduler + auto-sync on page open
Spec asks for a 1x/day sync while opted in and fresh data on every page
open. The scheduler only arms itself when RADAR_ENABLED AND the opt-in are
already on (boot) or right after the user opts in (settings route) — a
flag-off install never creates the timer, preserving the inertia contract.
The page auto-syncs once per mount when the cached feed is older than 6h.
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
|
||
|
|
0e1f40ed1f |
feat(oauth): add Raycast Pro provider with local auto-import (#8895)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
2a94cbfe14 |
feat(executors): add isolated Claude Code bridge over Devin ACP (#8914)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
8e27f5ec8d |
fix(sse): back the CCR block store with a durable tier (#9061) (#9198)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip) |
||
|
|
5ea43c7a9d |
feat: make forwarded upstream response-header budget configurable (#9243) (#9492)
Validated in local merge-train (diegosouzapw batch) |
||
|
|
2ddbbc61a6 |
[v3.8.50] feat(memory): MemoryBackend provider pattern with generic HTTP connector (#8752)
Validated in local merge-train T7 (ungrouped batch 2) |
||
|
|
51f9ffc007 |
[v3.8.50] feat(services): add Dario as a 5th embedded service (Claude Code toggle/failover) (#8523)
Validated in local merge-train T7 (ungrouped batch 2) |
||
|
|
4a6871381f |
[v3.8.50] feat(images): add POST /v1/images/upscale (Adobe Firefly Topaz + Stability + Topaz Labs) (#8791)
Validated in local merge-train T7 (ungrouped batch 2) |
||
|
|
e7f6b1d130 |
feat(radar): flag-gated signed free-model catalog overlay (#9515)
* feat(dashboard): add RADAR_ENABLED flag (default off) * feat(db): radar feed cache + settings with encrypted supporter key * feat(radar): signed feed sync with pinned key and version floor - feedSchema.ts: Zod v4 schema mirroring the server feed format (discriminated union on budget.kind, enum constraints, etc.) - pinnedKeys.ts: Ed25519 SPKI-DER pinned key + env override for forks - verify.ts: signature verification over exact wire bytes, never throws - sync.ts: full download/verify/validate/cache pipeline with injectable deps, feature-flag gate, opt-in gate, version floor (numeric compare), and sanitized error reasons (no stack traces) - 40 tests covering: contract hash, key handling, sig verification, schema validation, version compare, all sync paths (disabled, opt_out, invalid_signature, invalid_schema, stale, updated, error), auth header injection, and cache-untouched assertions for every failure mode * feat(radar): read-time overlay merge rules over the free catalog Pure function applyFeed() merges the cached Radar feed over the static baseline catalog at read time, honoring 4 rules: 1. Feed never overwrites a local override field. 2. enabled:false disables the entry with disabledBy:"radar" provenance. 3. User-added entry NOT in the feed survives untouched. 4. User deletion tombstone prevents feed resurrection. getRadarCatalog() accessor in index.ts: flag off / no cache / corrupt payload all fall back to baseline. Valid cache applies the overlay and returns feed metadata (version, tier, fetchedAt). TDD: 19 tests (4 rules + dedup + origin + accessor flag/cache/corrupt/ valid/bad-feed + baselineToMergedEntries converter). * feat(dashboard): radar catalog and guided setup screens - API routes: GET /api/radar/catalog, POST /api/radar/sync, POST /api/radar/settings - All gated on RADAR_ENABLED flag (404 when off) - Error responses via buildErrorBody(), never raw stack/message - Settings never echoes clear supporter key (masked omr_****<last4>) - Sync delegates to syncRadar() server-side, never proxies feed URL - Dashboard pages: - /dashboard/radar: 4 states (flag off, opt-in pending, empty, populated) - /dashboard/radar/setup?provider=X: guided setup with steps, key URL, test connection - Uses existing Card component and next-intl patterns - Sidebar: radar entry in costs group with icon - i18n: pt-BR and en keys for radarPage and radarSetupPage namespaces - Tests: - radar-api-routes.test.ts: 11 tests (flag-off 404, flag-on shape, error sanitization) - radar-page-state.test.ts: 5 tests (pure state logic) - All 90 radar tests pass (including prior 74) * docs(radar): module doc and flag-off inertia test Add docs/frameworks/RADAR.md covering the flag gate, the separate data-sync opt-in and privacy promise, the Ed25519 signature/pinned-key security model, tiers, the read-time overlay merge rules, and the self-hosting env vars — plus index entries in CLAUDE.md/AGENTS.md/docs/README.md/REPOSITORY_MAP.md. Document RADAR_FEED_URL and RADAR_FEED_PUBKEY in .env.example and docs/reference/ENVIRONMENT.md to satisfy check:env-doc-sync, which was failing on this branch since the sync.ts commit added the reads. Add tests/unit/radar-inertia.test.ts as the single canonical place asserting the "RADAR_ENABLED off => zero behavioral delta" claim end to end: the three /api/radar/* routes 404, the flag resolves to the definition default with no override, getRadarCatalog() returns exactly the baseline without touching the cache, and computeFreeModelTotals() keeps its pinned values with the Radar module imported alongside it. * fix(db): renumber radar migration to 135 after collision with 134 The base branch introduced 134_proxy_logs_egress_ip while this branch carried 134_radar_cache_settings; the migration runner rejects duplicate numeric prefixes. This migration has never been applied to a real database (the PR is unmerged), so no retroactive isSchemaAlreadyApplied guard is needed. * i18n(radar): translate radar catalog and setup strings to all locales The UI-coverage ratchet measures (present - placeholder) / total_en, so the __MISSING__ sentinels that i18n:sync-ui writes do not count as covered — only real translations restore the metric. Scoped to this PR's namespaces (radarPage, radarSetupPage, sidebar.radar*) instead of a bulk sync, which would have pulled ~978 unrelated pending keys into this diff. Placeholders and code identifiers verified preserved across all 1682 strings. * fix(radar): trust the served-tier header instead of the signed body field The signed feed body always carries tier:"live" by design (one signed artifact per version — rewriting the field server-side per request would break the exact-bytes Ed25519 signature). The server now returns the tier ACTUALLY served via the x-omniroute-feed-tier response header, so free users on a delayed community snapshot no longer see "Ao vivo (tempo real)" in the UI. sync.ts now reads and validates that header (falling back to the body's tier only when the header is absent or holds an unrecognized value) and stores the served tier in the cache; index.ts already surfaces cache.tier to the UI unchanged. * test(combo): shorten an assert message that exceeded the line limit The assertion added by #9507 was 104 chars, so prettier reformatted it into five lines on the next commit that touched the file, pushing it past its frozen size (3449) and failing check:file-size. The message is shortened (the issue reference stays in the comment directly above); the assertion itself is unchanged, and the file is back to 3448 lines and prettier-clean. * i18n(radar): use the canonical zh-TW glossary terms The machine translation produced retired renderings the glossary gate blocks: 供應商 for provider (canonical 提供者) and 文檔 for documentation (canonical 文件). Fixed across the 11 affected radar strings; tests/unit/i18n-glossary-consistency-check.test.ts is back to 17/17. * fix(radar): point the default feed URL at the domain that exists radar.omniroute.dev was a placeholder for a domain that was never registered, so an out-of-the-box sync would fail DNS resolution for every user. The live feed is served from radar.omniroute.online (the subdomain the design always specified), now behind Cloudflare TLS. Forks still override it via RADAR_FEED_URL. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
04683029a6 |
fix(build): exec native esbuild binary directly in prepublish (dast-smoke base-red) (#9558)
* fix(build): exec native tool binaries directly in runBuildTool #8858 routed every resolved local bin through process.execPath to avoid Windows .cmd shims — but esbuild >=0.25 ships bin/esbuild as the NATIVE platform executable (ELF on Linux), so Node parsed machine code as JS and build:cli died with 'SyntaxError: Invalid or unexpected token', turning dast-smoke red for every PR. runBuildTool now sniffs the entry's magic bytes (ELF / Mach-O / PE) and execs native binaries directly; JS entries keep going through this Node binary (the .cmd-shim avoidance #8858 wanted). Validation (RED->GREEN on this box): - RED: node node_modules/esbuild/bin/esbuild --version -> SyntaxError (ELF) - GREEN: the exact failing CI step reproduced via the new logic bundles open-sse/mcp-server/server.ts successfully (4.2MB output, 1.3s). * fix(docs): add MDX frontmatter to the 20 remaining docs without it Same failure class as AGENTROUTER_WAF (#9503) and DOCKER_RELEASE_CHANNELS (this run's dast-smoke red): any doc without frontmatter breaks the fumadocs MDX loader during next build, killing build:cli/dast-smoke for every PR. Swept ALL of docs/ (i18n mirrors excluded) in one pass so this class cannot recur one file at a time. * docs(env): document OMNIROUTE_INTERNAL_SERVICE_TOKEN(+_FILE), OPENROUTER_PROVIDER_STATS_* and embedded-Redis binding vars Pre-existing env/docs contract drift from recently merged features made check:env-doc-sync red for any docs-touching PR. Values and defaults read from the defining modules (internalServiceAuth.ts, openrouterProviderStats.ts). * fix(build): resolve bundled npm-cli.js in the standard Unix layout + safe npm fallback off-Windows The opencode-plugin step hard-failed on GitHub runners because resolveBundledNpmEntry only looked next to the node binary (Windows zip layout); hostedtoolcache Node keeps npm at <prefix>/lib/node_modules/npm. Added that candidate, and when neither exists on non-Windows the step now falls back to plain 'npm' — the .cmd-shim hazard #8858 avoids is Windows-only. * test(mutation): register xai-agent-tools-passthrough.test.ts in stryker tap.testFiles The test landed on release/v3.8.50 covering open-sse/handlers/chatCore/passthroughHelpers.ts without the stryker registration, so Fast Quality Gates' drift detection reds any PR that carries it. Mechanical registration so its mutant kills count. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |