Conversation
… widen to lg (diegosouzapw#6265) (diegosouzapw#6526) fix(providers): size AddApiKeyModal for 1080p (diegosouzapw#6265). Integrated into release/v3.8.47. (thanks @chirag127)
…egosouzapw#6240) (diegosouzapw#6511) * fix(api): exempt test-model requests from Output Styles injection (diegosouzapw#6240) Root cause: handleChatCore's Phase 4A Output Styles injection (chatCore.ts) was gated only by the operator's global compression.enabled switch, independent of the per-request x-omniroute-compression header. The dashboard 'Test model' action (modelTestRunner.ts) never sent that header, so a globally-enabled Output Style (e.g. 'Ultra terse') always leaked its system-prompt injection into a plain connection test. Fix: skip Output Styles injection when the request explicitly opts out via x-omniroute-compression: off, and always send that header from buildInternalChatRequest / buildInternalRerankRequest. Regression guard: tests/integration/test-model-compression-off-6240.test.ts, tests/unit/model-test-runner-compression-off-6240.test.ts * chore: sync CHANGELOG to release tip (diegosouzapw#6511; bullet re-added at merge)
…tion (diegosouzapw#6402) (diegosouzapw#6515) fix(api): return 400 for missing/invalid messages before model resolution (diegosouzapw#6402). Integrated into release/v3.8.47. (thanks @chirag127)
…pw#6304) (diegosouzapw#6510) * fix(providers): spawn Auggie CLI with shell:true on win32 (diegosouzapw#6304) * chore: sync CHANGELOG to release tip (diegosouzapw#6510; bullet re-added at merge)
…h + surface substitution (diegosouzapw#6463) (diegosouzapw#6534) fix(compression): honor UI-toggled engines in stackedPipeline dispatch + surface substitution (diegosouzapw#6463). Integrated into release/v3.8.47. (thanks @chirag127)
…eout (diegosouzapw#6458) (diegosouzapw#6546) fix(providers): fail fast on empty auto-combo pool instead of 15s timeout (diegosouzapw#6458). Integrated into release/v3.8.47. (thanks @chirag127)
diegosouzapw#6400) (diegosouzapw#6517) fix(api): add explicit HEAD handler for /v1/models to prevent ~6s hang (diegosouzapw#6400) Integrated into release/v3.8.47. (thanks @chirag127)
…ed-fallback loops (diegosouzapw#6328) (diegosouzapw#6549) fix(models): apply hidePaidModels to synced/custom/alias-backed/managed-fallback loops (diegosouzapw#6328) Integrated into release/v3.8.47. (thanks @chirag127)
…dModels=true (diegosouzapw#6328) (diegosouzapw#6551) fix(backup): exclude paid models from JSON export/backup when hidePaidModels=true (diegosouzapw#6328) Integrated into release/v3.8.47. (thanks @chirag127)
…souzapw#6461) (diegosouzapw#6519) fix(compression): surface fallback reasons in preview response (diegosouzapw#6461). Integrated into release/v3.8.47. (thanks @chirag127)
…catalog + test endpoints (diegosouzapw#6328) (diegosouzapw#6552) fix(dashboard-api): apply hidePaidModels to /api/models + openrouter-catalog + test endpoints (diegosouzapw#6328). Integrated into release/v3.8.47. (thanks @chirag127)
…hidePaidModels=true (diegosouzapw#6328) (diegosouzapw#6550) fix(autoCombo): exclude paid-tier auto/* ids from the catalog when hidePaidModels=true (diegosouzapw#6328). Integrated into release/v3.8.47. (thanks @chirag127)
…ent-Type (diegosouzapw#6414) (diegosouzapw#6513) fix(api): return 415 on /v1/messages for non-JSON Content-Type via requireJsonContentType middleware (diegosouzapw#6414) Integrated into release/v3.8.47. (thanks @chirag127)
…es in fusion 503 (diegosouzapw#6454) (diegosouzapw#6521) fix(providers): honor fusion minPanel=1 and surface per-member failures in fusion 503 (diegosouzapw#6454) Integrated into release/v3.8.47. (thanks @chirag127)
… clear error (diegosouzapw#6457) (diegosouzapw#6525) fix(providers): reject image-only models on /v1/chat/completions with a clear error (diegosouzapw#6457) Integrated into release/v3.8.47. (thanks @chirag127)
…bug-fix bullets to v3.8.47 (diegosouzapw#6596)
… fetch (diegosouzapw#6379) (diegosouzapw#6600) orderTargetsByHeadroom already loaded the per-connection DB snapshot (with decrypted credentials) via expandTargetsByQuotaAwareConnections, but discarded it before calling getSaturation. For Codex, fetchCodexSaturation forwards straight to fetchCodexQuota(connectionId, connection), which needs the connection object (or a prior registerCodexConnection() call that never happens before headroom ranking runs) to read accessToken. Without it, fetchCodexQuota returned null for every candidate, saturation failed open to 0 across the board, and headroom ranking fell back to the original combo order regardless of actual free quota. getSaturation() and the headroom SaturationFetcher seam now accept and thread the loaded connection snapshot through to fetchCodexQuota. Regression guard: tests/unit/headroom-codex-quota-snapshot-6379.test.ts (seeds two real Codex connections in a throwaway SQLite DB with a fake upstream fetch, confirms RED on unfixed code, GREEN after the fix).
…g catch it (diegosouzapw#6404) (diegosouzapw#6603) playground-api-tab.test.tsx's SSE test always took the disabled-button branch (the fetch mock returned an empty model list) and asserted a tautology instead of exercising the SSE path it claims to verify. The test now selects a real model to enable Send, asserts it is actually enabled, and asserts the streamed SSE content reached the response editor. check-test-masking.mjs's tautology subcheck only compares base-vs-HEAD counts within a PR's own diff and no-ops entirely outside PR context (no GITHUB_BASE_SHA/REF) -- so a tautology merged once, or checked with a bare local run, stayed invisible forever after. Added an always-on absolute-floor scan (scanBareTautologies/countBareTautologies) over every tracked test file, scoped to the bare expect(true).toBe(true)/assert.equal(1,1) patterns that have zero legitimate uses in this codebase -- deliberately excluding assert.ok(true), which has ~15 pre-existing verified-legitimate try/catch-fallback uses and stays on the lenient diff-only path.
…iegosouzapw#6455) (diegosouzapw#6607) The fusion single-survivor degrade path (added for diegosouzapw#6454) returned the lone panel answer directly whenever only one panelist succeeded, ignoring an explicitly configured judgeModel. With default minPanel=2 and a 2-model panel, any single flaky panelist forced this path every request, so the configured judge never ran and the response .model reflected a panel member. The judge is now still invoked to synthesize a lone surviving answer when judgeModel is explicitly configured; the direct-answer shortcut is kept only for the implicit case (no judgeModel, judge defaults to panel[0]).
…lation (diegosouzapw#6459) (diegosouzapw#6609) appendToolCallArgumentDelta() treated any non-string incoming fragment as empty, silently dropping tool-call arguments delivered as an already-parsed JSON object/array (a non-conformant shape some upstreams emit for tool_calls[].function.arguments) instead of JSON-encoding them. This left tool_use.input empty on the /anthropic streaming path and opened the door to downstream [object Object] string coercion once buffers were concatenated. Now JSON.stringify()s the non-string fragment instead of discarding it.
…er fusion regression guard (diegosouzapw#6614) The fusion quorum-clamp/failure-detail root cause reported in diegosouzapw#6454 was already fixed and merged via diegosouzapw#6521 (open-sse/services/fusion.ts already carries Math.max(1, cfg.minPanel) + per-member failure reasons on this branch). That merge never landed a CHANGELOG bullet for diegosouzapw#6454 itself. Backfills the missing bullet and adds a regression test at the exact repro scale (11-member fusion-free-style panel, 2 cooling / 9 healthy) to lock in that a cooling minority no longer sinks a healthy majority, while a genuinely all-failed panel still returns the documented 503.
…diegosouzapw#6427) (diegosouzapw#6616) `validateResponseQuality()` only inspected a response's top-level `error` field when `choices` was also missing/empty (the narrower diegosouzapw#3424 case), so a masked HTTP 200 that echoed a non-empty stub `choices` alongside a structured error object — or a known exhaustion phrase like "insufficient credits" / "quota exceeded" in the error envelope — slipped through as valid, and a `priority` combo kept hammering the exhausted target instead of failing over. The check now inspects the error envelope (top-level `error` object, or a bounded exhaustion-phrase match against error.message/code/type and top-level message/detail) unconditionally, before any shape-specific branch — never against `choices[].message.content`, so legitimate completions that merely mention "quota" in prose are not misclassified. Regression guard: tests/unit/masked-200-exhaustion-fallback-6427.test.ts
…osts (diegosouzapw#6494) (diegosouzapw#6617) generateCert() hard-coded a single SAN entry (daily-cloudcode-pa.googleapis.com) while server.cjs terminates TLS locally for all 4 antigravity/cloudcode-pa hosts, so 3 of the 4 hosts served a cert whose CN/SAN didn't match and MITM interception failed for them. Source the host list from the existing authoritative ANTIGRAVITY_TARGET.hosts registry instead of a second hard-coded copy.
…souzapw#6946) getProviderConnections ignored the authType query param, causing callers like tokenHealthCheck.ts and /api/token-health to fetch and decrypt every connection instead of only the OAuth ones they asked for. Add the missing auth_type WHERE clause and a regression test. Rebased to drop the unrelated 46-icon commit (duplicate of diegosouzapw#6926) and the accidentally-committed tests/unit/authz/__stub_apiKeys.mjs runtime artifact; replaced with a real unit test asserting the authType filter excludes non-matching connections. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…ting/gh checks (diegosouzapw#6947) ROTATING_REFRESH_PROVIDERS.has(conn.provider) fails for 'OpenAI' or 'Github' - the set is all lowercase. Same issue for the GitHub Copilot sub-token refresh guard. Both now normalize to lowercase before comparison, matching the established pattern from getHealthCheckSkipProviders() (line 201) and isGitHubAccessTokenOnlyConnection() (line 94). Replaces the whole-file regex assertion in oauth-providers-error-handling (which passed even on unfixed code) with two statement-scoped regression tests that fail against origin/release/v3.8.47's unfixed source and pass only once both call sites are normalized. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…iegosouzapw#6930) * perf: thread pre-fetched token to checkRateLimit avoiding re-query getRelayTokenByHash already fetches the full RelayToken row. A few lines later checkRateLimit(token.id) does a second SELECT * FROM relay_tokens on a different predicate (id instead of token_hash). Change: - checkRateLimit accepts an optional existingToken parameter; when provided, skips the re-query entirely. - Both relay routes (chat completions + bifrost) pass the already- fetched token. - The function now uses RelayToken (camelCase) instead of RelayTokenRow (snake_case) when the token is passed in. PR-URL: fix-relay-thread-token * test(db): add regression coverage for checkRateLimit existingToken fast-path Adds node:test coverage for src/lib/db/relayProxies.ts::checkRateLimit proving the existingToken fast-path (pre-fetched RelayToken threaded in, no re-query) agrees with the legacy re-query path (no token passed), and that the per-minute cap is still enforced through the fast-path. Also adds a changelog.d fragment for the perf fix in 9d4cd90. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…iegosouzapw#6929) enforceApiKeyPolicy() already fetches the API key metadata and returns it as policy.apiKeyInfo. The old code at line 72 called getApiKeyMetadata a third time per request (third hash+DB query after isValidApiKey and enforceApiKeyPolicy's internal fetch). Change: use policy.apiKeyInfo directly instead of re-querying. Also removes the now-unused getApiKeyMetadata import. Adds a regression test exercising the dashboard-playground-key path (no bearer token, only enforceApiKeyPolicy's resolvePlaygroundTestKey fallback resolves the key) — the old apiKeyRaw-gated call always produced a null apiKeyMeta on that path, while policy.apiKeyInfo correctly carries it through to the downstream call log. Split out of the original PR: dropped the unrelated 46-provider-icon commit that had been bundled onto the same branch. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…onses input (diegosouzapw#6932) codex-cli replays assistant history with content parts typed as `input_text`, but the Responses API only accepts `output_text` (or `refusal`) on assistant turns — `input_text` is user-only. `normalizeCodexMessageContentPart` previously only rewrote parts literally typed `text`, leaving explicit `input_text` on assistant turns untouched, which the Codex/OpenAI backend rejects with a 400. Rewrite explicit `input_text` (and `text`) to `output_text` on assistant-role parts, dropping the assistant-only `annotations`, `logprobs`, and `obfuscation` fields. Mode-agnostic, applies to all Codex models. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…njection (diegosouzapw#6943) * fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (diegosouzapw#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes diegosouzapw#6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (diegosouzapw#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (diegosouzapw#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (diegosouzapw#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes diegosouzapw#6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from diegosouzapw#6605 which passed the smoke. Not this diff.) * fix(6813): fix thinking budget zero drop and default thinkingConfig injection - Fix truthy check for budget_tokens to allow 0 - Stop injecting default thinkingConfig when no knobs present - Add tests covering all scenarios Related: diegosouzapw#6813 --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#6590) Add checkConnectionCapacity guard with 429 + Retry-After in handleChat(). Introduce OMNI_MAX_CONCURRENT_CONNECTIONS env-bound cap, disabled (0) by default so existing deployments are unaffected until an operator opts in. Reconstructed from PR diegosouzapw#6590, isolating only the backpressure change — the original branch also carried unrelated headroom/docker/perf work from the author's separate diegosouzapw#6572 branch. Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…re-export + OMNI_MAX_CONCURRENT_CONNECTIONS docs)
…souzapw#6952) (diegosouzapw#6990) The diegosouzapw#6199/diegosouzapw#6561 commentary-phase filter (shouldDropResponsesCommentaryEvent) was wired only into createSSEStream's PASSTHROUGH branch. The TRANSLATE-mode loop (openai-responses upstream -> another client format, e.g. codex routes streaming into Claude Code) called translateResponse() on every raw chunk without checking phase, so internal commentary-phase scratchpad text leaked into the client-visible content channel as duplicate prose and narrated tool-call arguments. Extends the same stateful filter into TRANSLATE mode via a small factory (createTranslateCommentaryFilter) that owns its own item/index Sets, keeping the wiring in stream.ts (a frozen file) to a single guarded line. Fail->pass evidence: - tests/unit/repro-6952-commentary.test.ts against origin/HEAD (pre-fix): FAILED - "commentary-phase prose must not reach the translated client stream" - Same test against the fix: PASSED (2/2)
…names in builder options (diegosouzapw#6975, diegosouzapw#6957) (diegosouzapw#6991) Removes the leftover chat-only isChatCapable gate from addModelOption() (diegosouzapw#6975) and adds a name-disambiguation pass at the end of buildModelOptions() so distinct model ids sharing the same upstream display name fall back to their id (diegosouzapw#6957). Both proven with TDD repro tests (RED->GREEN).
…es (diegosouzapw#6951) (diegosouzapw#6992) stripEmptyOptionalToolArgs was allowlist-only (Read/Subagent) and only stripped empty-string/empty-array values, so Responses API strict mode (every property forced into `required`) could forward a forced non-empty value (e.g. Agent.isolation) or a schema-declared default value verbatim to the client. Add schema-aware drop-if-default and generalized drop-if-empty (any tool, gated on schema.required), and thread each tool's JSON Schema from the request's tools[] into the two streaming call sites (response.output_item.done handling). Closes diegosouzapw#6951
…osouzapw#6786 regression), migration gap 121, file-size freeze bumps
…souzapw#6912 max_tokens normalization (diegosouzapw#6967) The test asserted both max_tokens and max_completion_tokens=4096 on the nvidia/openai/gpt-oss-120b emergency fallback request. Commit a34fb6b (diegosouzapw#6912, merged into this release tip) added a symmetric normalization in chatCore.ts that renames/deletes the redundant max_completion_tokens field whenever the target provider supportsMaxTokens() (nvidia does), so only max_tokens reaches the upstream request. The old dual-field assertion is an outdated contract, not a regression. Align the test to the new intentional behavior while keeping the max_tokens=4096 cap assertion as the fallback-cap guard.
…n gemini defaults test with diegosouzapw#6943 (unit-full pre-flight)
…egosouzapw#6943), eslint suppression match, file-size/zizmor rebaselines
…39.3->38, i18nUiCoverage 76.8->75.5) with justification
Bundle recharts behind next/dynamic boundaries to prevent its
large module graph from being included in the initial JS payload.
- CostOverviewTab.tsx → dynamic(() => import('./components/CostCharts'))
- ProviderUtilizationTab.tsx → dynamic(() => import('./components/ProviderCharts'))
- BurnRateChart.tsx → dynamic(() => import('./components/BurnRateChartInner'))
- Created 3 wrapper files with 'use client' and all recharts imports
Reduces initial bundle by ~35 kB (recharts + dependencies).
…ovider-nodes
Add optional limit/offset parameters to DB list functions and their
API route handlers. All list functions now return { items, total } when
called with parameters; backward compatible when called without args.
Affected modules:
- lib/db/apiKeys.ts - listApiKeys, getApiKeysByGroup
- lib/db/combos.ts - listCombos
- lib/db/providers.ts - listProviders, getProvidersByGroup
- lib/db/providers/nodes.ts - listProviderNodes, getProviderNodesByGroup
- Corresponding API routes pass through query params
Reduces memory pressure on large datasets by returning one page at a time.
…appings, playgroundPresets
Add optional limit/offset parameters to DB list functions and their
API route handlers for the remaining data modules.
Affected modules:
- lib/db/webhooks.ts - getWebhooks returns { webhooks, total }
- lib/db/proxies.ts - listProxies
- lib/db/modelComboMappings.ts - listMappings
- lib/db/playgroundPresets.ts - listPresets
- Corresponding API routes pass through query params
- Re-exports updated: lib/localDb.ts, models/index.ts
Backward compatible: calling without args returns all rows.
Replace per-pool N+1 queries with batch-loading pattern. - Added batchBuildPools(rows) — collects all pool IDs, does 2 batch queries (allocations + connections) instead of 2N individual queries - getPoolsByGroup and listPools now use batchBuildPools - Added optional limit/offset pagination params - Fixed SQLite OFFSET-syntax bug: only emit OFFSET when LIMIT also present - Added quota-pools.test.ts with 10 tests covering pagination edge cases, batch loading, and the offset-without-limit guard Reduces pool-page query count from 2N+1 to 3 (constant).
… limit batchBuildPools builds an IN (...) query with one bound parameter per pool. With 999+ pools, SQLite rejects the query with 'too many SQL variables'. Split pool IDs into chunks of at most 999 and run one batch query per chunk, merging results into the same Maps. This keeps each query under SQLite's default bound-parameter limit.
…wind truncate in CostOverviewTab
…insights - Define missing handleToggleSource function in FreePoolTab.tsx after handleToggleSelect, following same immutable Set toggle pattern with saveDisabledSources() persistence - Add https://static.cloudflareinsights.com to both dev and prod script-src directives in next.config.mjs to resolve CSP violation
diegosouzapw
added a commit
that referenced
this pull request
Jul 18, 2026
…souzapw#7241) * feat(cli): add Grok Build CLI tool setup (~/.grok/config.toml) Registers xAI's Grok Build TUI coding agent as a configurable CLI tool in /dashboard/cli-code, so OmniRoute can write itself in as a custom model provider in ~/.grok/config.toml. Mechanism: Grok Build reads a TOML config that can hold several user-defined [model.*] sections plus a [models].default pointer. Unlike the sibling Forge handler (which owns its whole config file and can full-replace it), this one surgically upserts ONLY the [model.omniroute] section and rewrites [models].default, leaving every other section byte-intact. Apply records the previous default in an `# omniroute-prev-default` marker comment so Reset can restore the user's original default instead of guessing. Built on OmniRoute's existing CLI-tools infrastructure rather than replaying the upstream shape: getCliRuntimeStatus() for detection (no ad-hoc `which grok` exec), Zod validation via cliModelConfigSchema, the write guard, createBackup(), the cliToolState DB module, and sanitizeErrorMessage() for every error path (Hard Rule #12). Security: GET reaches getCliRuntimeStatus(), which spawns a child process to locate and healthcheck the `grok` binary. That is the same transitive-spawn surface that classified /api/skills/collect/, so the route is registered in LOCAL_ONLY_API_PREFIXES and loopback-enforced before any auth check (Hard Rules #15 + #17). Writing a local CLI's config file is inherently a local-machine operation, so this costs no real capability. Co-authored-by: rixzkiye <rizkiyemubarok05@gmail.com> Inspired-by: decolua/9router#2571 * chore(changelog): fragment for diegosouzapw#7241 * fix(cli): shrink cliTools.ts/cliRuntime.ts under the file-size ratchet + fix stale catalog counts The grok-build registry/runtime entries pushed cliTools.ts (916->932) and cliRuntime.ts (1128->1137) past their frozen file-size caps. Extract the grok-build entries into cliToolsGrokBuild.ts (registry, typed) and cliRuntimeGrokBuild.ts (runtime metadata, deliberately untyped/no cliCatalog import so it doesn't drag that schema file into the typecheck:core curated allowlist's transitive graph). The amp runtime entry rides along in the same runtime file for the extra headroom needed to clear cliRuntime.ts's cap with zero slack. Also update the two catalog-cardinality canaries (cli-tools-schema.test.ts, cli-catalog-counts.test.ts) and EXPECTED_CODE_COUNT to include grok-build: 20->21 visible code entries, 24->25 total code entries, 32->33 grand total. Fixes CI reds on diegosouzapw#7241 surviving a release/v3.8.49 merge: Fast Quality Gates (check:file-size) and Unit Tests fast-path (1/4, 2/4). * test(stryker): register grok-build route-guard test in tap.testFiles check:mutation-test-coverage --strict flagged tests/unit/route-guard-grok-build-settings-local-only.test.ts as a covering unit test for src/server/authz/routeGuard.ts missing from stryker.conf.json's tap.testFiles allowlist (only became reachable once the Fast Quality Gates job got past the file-size fix earlier in this branch). --------- Co-authored-by: rixzkiye <rizkiyemubarok05@gmail.com>
diegosouzapw
added a commit
that referenced
this pull request
Jul 23, 2026
… (diegosouzapw#7966) * fix(api): classify /api/acp/agents as loopback-only (diegosouzapw#7948) /api/acp/agents is spawn-capable (POST registers a client-chosen `binary` via src/lib/acp/registry.ts; GET / POST {action:"refresh"} runs detectInstalledAgents() -> execFileSync(probe.command, probe.args, { shell }) transitively) but was never added to LOCAL_ONLY_API_PREFIXES in src/server/authz/routeGuard.ts, unlike every sibling spawn-capable route (Hard Rules #15/#17). A leaked JWT via tunnel could reach the version-probe execFileSync path. Adds the prefix to the loopback gate plus a direct regression test (tests/unit/route-guard-acp-agents-local-only.test.ts) and an assertion in tests/unit/check-route-guard-membership.test.ts documenting why the existing source-scan subcheck cannot catch this class of gap (the spawn call is transitive via registry.ts, not in the route file itself). * chore(quality): register route-guard-acp-agents-local-only test in stryker tap.testFiles
diegosouzapw
added a commit
that referenced
this pull request
Jul 23, 2026
…iders (diegosouzapw#7892) * chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (diegosouzapw#7168) * feat(vnc-session): persistent noVNC browser login for web cookie/token providers ## Why (the headless-install problem) OmniRoute's web cookie/token providers (ChatGPT Web, Gemini Web, Claude Web, DeepSeek Web, …) need a live browser session, but the gateway normally runs **headless** — as a systemd service, inside Docker, or on a VPS with no display. There is no desktop for the operator to log into the provider in. Today the operator has to obtain the session cookie/token *out of band* (open a real browser elsewhere, export cookies, paste them into the connection row). That is fiddly, breaks on every provider UI change, and is a non-starter on a headless box where you can't open a browser at all. This PR adds an **on-demand interactive login**: OmniRoute boots a containerized browser that exposes a noVNC web UI at the host. The operator opens that URL in *their own* browser, logs in normally, and OmniRoute then harvests the resulting cookies / localStorage back into the provider's `provider_connections` row over the DevTools Protocol. No display required on the host — the headless server renders the login into a container and the human just drives it through a web page. ## How we ran into this - The shipped `dist/` bundle has **no App Router source**, so the only visible seam was `dist/server-ws.mjs`'s `http.createServer` monkeypatch. That seam is **dead**: Next's standalone `startServer` creates its own http server in a way that bypasses the override, so a route registered there never fires (debug logs confirmed: zero requests reached it). The real seam is the Next **App Router** (`src/app/api/...`), which lives in the dev tree, not `dist/`. - **Chromium ≥130 forces the remote-debugging port onto `127.0.0.1`** and ignores `--remote-debugging-address=0.0.0.0`. A plain published port can't reach it, so cookie harvest needs an in-container TCP bridge to republish the loopback CDP onto `0.0.0.0`. We shipped that bridge, but the cleaner default is **Firefox** (`jlesage/firefox`): its debugger binds `0.0.0.0` out of the box, so harvest works with no bridge at all. - The CDP harvester **hung forever** on the first tries: the message handler was defined but never attached to the socket, so every `send()` promise stayed pending. We replaced Playwright's `connectOverCDP` (which stalls through the bridge) with a **raw `ws` client** and wired the handler — now resolves. ## What New management API (scoped like the other admin endpoints via `requireManagementAuth`): | Method | Path | Purpose | | --- | --- | --- | | GET | `/api/vnc-session` | list active sessions + supported providers | | GET | `/api/vnc-session/:provider` | session state | | POST | `/api/vnc-session/:provider/start` | boot browser container → returns `vncUrl` | | POST | `/api/vnc-session/:provider/harvest` | persist cookies into the provider row | | POST | `/api/vnc-session/:provider/touch` | defer idle auto-stop | | DELETE | `/api/vnc-session/:provider` | stop + remove the container | ## Implementation - `src/lib/vncSession/manifest.ts` — provider → login URL + cookie/token map + config - `src/lib/vncSession/harvest.ts` — raw-CDP cookie/localStorage harvester (`ws`) - `src/lib/vncSession/service.ts` — docker lifecycle, port allocation, idle sweep, DB write - `src/app/api/vnc-session/**` — App Router routes - `src/lib/gracefulShutdown.ts` — tears down running login containers on exit ## Browser image choice Default is **`jlesage/firefox`** (0.0.0.0-friendly CDP, no bridge). The Chromium image + in-container bridge lives under `docker/vnc-browser/chromium`, selectable via `OMNIROUTE_VNC_IMAGE`. See `docker/vnc-browser/README.md`. ## Config (env) `OMNIROUTE_VNC_IMAGE`, `OMNIROUTE_VNC_CONTAINER_VNC_PORT`, `OMNIROUTE_VNC_CONTAINER_CDP_PORT`, `OMNIROUTE_VNC_PROFILE_DIR`, `OMNIROUTE_VNC_IDLE_MS`, `OMNIROUTE_VNC_MAX_MS`, `OMNIROUTE_VNC_MAX_SESSIONS`, `OMNIROUTE_DOCKER_BIN` — all documented in the docker README. ## Tests `tests/unit/vnc-session.test.ts` — manifest lookup + credential mapping (cookie / token / whole-jar). All passing via the Node test runner. ## Notes - Docker is the only external dependency; if the `docker` CLI is missing, `start` throws a clear error and shutdown is a no-op. - No secrets are returned by any endpoint — only session metadata + ports. Co-authored-by: Sora <138304505+Capslockb@users.noreply.github.com> Co-authored-by: Bernardo <138304505+Capslockb@users.noreply.github.com> * refactor(vnc-session): derive provider credentials from shared contract * fix(vnc-session): harden CDP harvesting and credential filtering * refactor(vnc-session): scope lifecycle to provider connections * fix(vnc-session): sanitize and scope management routes * fix(vnc-session): use canonical provider list in API * test(vnc-session): align coverage with canonical manifest * docs(vnc-session): align browser setup with current implementation * fix(security): loopback-gate /api/vnc-session (Hard Rule #15/#17) The new /api/vnc-session/* routes spawn Docker containers via child_process.spawn (src/lib/vncSession/service.ts) but were never registered in LOCAL_ONLY_API_PREFIXES or SPAWN_CAPABLE_PREFIXES, so they were reachable from non-loopback callers (any manage-scope API key or dashboard session over a tunnel) - the same CVE class (GHSA-fhh6-4qxv-rpqj) those constants exist to close. Register VNC_ROUTE_PREFIX (already exported but unused in manifest.ts) in both prefix lists, and add a regression test asserting isLocalOnlyPath()/isLocalOnlyBypassableByManageScope() correctly classify the new prefix. Co-authored-by: CAPSLOCKB <138304505+Capslockb@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes
1.
handleToggleSourceReferenceError in FreePoolTab.tsxhandleToggleSourcefunction betweenhandleToggleSelectandhandleBulkAddsetDisabledSources, persistence viasaveDisabledSources()ReferenceError: handleToggleSource is not defined2. CSP violation blocking
https://static.cloudflareinsights.com/beacon.min.jshttps://static.cloudflareinsights.comto both dev and prodscript-srcdirectives innext.config.mjsVerification
handleToggleSourceproperly defined and referenced at all call sitesCloses # (reference issue if applicable)