Commit Graph

18 Commits

Author SHA1 Message Date
AmirHossein Rezaei
df9550fce9 fix(cli): prepare Next.js cache dir on Android/Termux before serve (#8593)
* fix(cli): ensure `~/.cache` is created and `XDG_CACHE_HOME` is set before Next.js loads on Android/Termux to prevent silent HTTP 500 errors due to instrumentation hook failures (#8519)

* chore(quality): ignore XDG_CACHE_HOME in the env/docs contract scanner

XDG_CACHE_HOME is an XDG Base Directory spec variable set by the OS or the
operator, never OmniRoute product config — the same reason XDG_CONFIG_HOME is
already ignored. The Android/Termux cache-dir preparation added here reads it
to honor an operator-set cache location, which made check-env-doc-sync demand
an .env.example/ENVIRONMENT.md entry for a variable we do not own.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* build(pack): require bin/cli/utils/ensureAndroidCacheDir.mjs in the tarball

bin/omniroute.mjs imports this module at startup to prepare the Next.js cache
dir before serve on Android/Termux. bin/cli/ is only an allowlist PREFIX, so a
file missing from the tarball would not fail the unexpected-paths check — it
would ship a CLI that throws ERR_MODULE_NOT_FOUND on the very platform this
change targets. Registering it makes the absence loud, same guard class as
storageKeyProvision.mjs and versionFastPath.mjs.

Caught by tests/unit/pack-artifact-entrypoint-closures.test.ts in the v3.8.49
merge-train.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-27 19:07:19 -03:00
Diego Rodrigues de Sa e Souza
40bd70c9cd fix(cli): surface the real spawn error in process supervisor (#8091) (#8158) 2026-07-22 11:28:20 -03:00
Diego Rodrigues de Sa e Souza
6302a78657 fix(db): register SIGHUP handler and stop force-killing server on win32 stop paths (#8045) (#8148)
Windows console-window close delivers CTRL_CLOSE_EVENT, which Node/libuv maps
to a JS-visible SIGHUP event. initGracefulShutdown() only listened for
SIGTERM/SIGINT, so closing the window never ran cleanup() (WAL checkpoint +
closeDbInstance()), leaving storage.sqlite's WAL un-checkpointed for the next
launch.

Separately, process.kill(pid, "SIGTERM") on win32 unconditionally
force-terminates the target process instead of delivering an interceptable
signal. The CLI's own stop paths (ServerSupervisor.stop() and
runStopCommand()) sent it immediately on every stop, racing and beating the
child's own async graceful shutdown before the WAL checkpoint could run.

Fix:
- src/lib/gracefulShutdown.ts: register a SIGHUP handler alongside
  SIGTERM/SIGINT.
- src/shared/platform/windowsProcess.ts (new): stopProcessGracefully() skips
  the immediate SIGTERM on win32 (letting the target's own CTRL_C/CTRL_CLOSE
  handling run) and polls before escalating to SIGKILL; unchanged immediate
  SIGTERM behavior on POSIX.
- bin/cli/runtime/processSupervisor.mjs and bin/cli/commands/stop.mjs: use
  stopProcessGracefully() instead of an unconditional process.kill(SIGTERM).

Regression tests: tests/unit/graceful-shutdown-sighup-8045.test.ts (reuses
the RED probe from the triage analysis) and
tests/unit/windows-process-stop-8045.test.ts.
2026-07-22 11:27:49 -03:00
Arul Kumaran
edbce2e35b feat: support Bun bundled SQLite runtime (#7878)
* feat: support Bun bundled SQLite runtime

* fix: harden Bun SQLite backups and params

* docs: keep Node as the only supported runtime; document bun:sqlite as best-effort compatibility path

Co-authored-by: Arul Kumaran <arul@luracast.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-21 16:30:26 -03:00
Diego Rodrigues de Sa e Souza
a62141210b fix(cli): verify better-sqlite3 native binary is actually loadable (#7105)
* fix(cli): verify better-sqlite3 native binary is actually loadable (port from 9router#2493)

isBetterSqliteBinaryValid() only checked the .node file's magic bytes (ELF/Mach-O/PE header), never whether the binary was built for the ABI (NODE_MODULE_VERSION) of the Node runtime that loads it. A stale or foreign-ABI binary passed the check and then segfaulted the process on the first database call instead of triggering a rebuild via npmInstallRuntime(). The fix adds a real load probe (require() in a throwaway subprocess) after the magic-byte check, so an incompatible binary is now correctly reported as invalid and the runtime self-heal reinstalls it.

Reported-by: Manikandan (@mrprohack) (https://github.com/decolua/9router/issues/2493)

* chore(changelog): move #2493 entry to changelog.d fragment

Consistency with the repo's canonical changelog.d/fixes/ workflow (avoids
merge-storm re-conflicts from editing CHANGELOG.md directly).
2026-07-16 14:13:33 -03:00
Diego Rodrigues de Sa e Souza
318b0b01d2 fix(cli): surface a diagnostic instead of a silent hang on serve readiness timeout (#6321) (#6504)
fix(cli): surface a diagnostic instead of a silent hang on serve readiness timeout (#6321). Integrated into release/v3.8.47.
2026-07-07 17:46:07 -03:00
Diego Rodrigues de Sa e Souza
78f09c8d9f Release v3.8.41 (#5327)
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors).

All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke.

Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
2026-06-29 16:51:03 -03:00
Diego Rodrigues de Sa e Souza
555b21d296 Release v3.8.37 (#5053)
* chore(release): open v3.8.37 development cycle

* chore(ci): harden release flow — ratchet decoupling, fast-path drift gates, build-scope guard, heap default (#5054)

Implements improvements 1-4 from the v3.8.36 release benchmark (_tasks/release-bench/v3.8.36/PLANO-MELHORIA.md):

1. Quality Ratchet decoupled from flaky coverage (ci.yml): the shard→coverage→ratchet
   chain meant a single flaky Coverage Shard SKIPPED the whole Quality Ratchet on the
   release PR (v3.8.36 #4854), so cycle drift only surfaced post-merge in #5029. The job
   now runs on !cancelled(); coverage download is continue-on-error and the ratchet runs
   --allow-missing, so the DETERMINISTIC gates (eslint/complexity/cognitive/duplication/
   codeql) stay blocking even when coverage is unavailable.

2. Fast-path drift gates (quality.yml PR→release): added check:complexity,
   check:cognitive-complexity, and a new lightweight check:pack-policy (pack-artifact
   unexpected-files check WITHOUT a build, via --policy-only) so drift + stray-tarball-file
   regressions are caught/rebaselined PER-PR instead of cascading onto the release PR.

3. Build heap default 4096→8192 MB (build-next-isolated.mjs): the clean graph peaks
   ~3.9 GB and brushed the old 4 GB ceiling; 8 GB gives headroom. Comment notes heap is
   NOT the fix for a poisoned scope (run check:build-scope instead).

4. check:build-scope gate (new): fails if .ts/.tsx/.js/.jsx files in the tsconfig scope
   exceed a threshold — catches worktrees/cruft leaking into the build scope (the v3.8.36
   OOM root cause: 355,215 vs 4,547 files) BEFORE it detonates next build. Wired into the
   fast-path.

* fix(auth): only trust forwarding headers from loopback TCP peers (#4689)

Integrated into release/v3.8.37 — loopback-gated forwarding headers (IP spoofing fix). Cherry-picked onto current release tip; ipUtils.test.ts 9/9 green.

* fix(codex): treat OAuth 401 as unrecoverable refresh failure (#4686)

Integrated into release/v3.8.37 — codex OAuth 401 treated as unrecoverable refresh. Cherry-picked onto release tip; token-refresh-service.test.ts 38/38 green.

* fix(translator): preserve reasoning_effort for non-Copilot Responses clients (#4688)

Integrated into release/v3.8.37 — preserve reasoning_effort for non-Copilot Responses clients. Cherry-picked onto release tip; tests 47/47 green.

* fix(translator): coerce tool descriptions to strings in OpenAI normalization (#4675)

Integrated into release/v3.8.37 — coerce tool descriptions to strings in OpenAI normalization. Cherry-picked onto release tip; tests 3/3 green.

* feat(sse): x-omniroute-strip-reasoning header to drop reasoning_content (#4678)

Integrated into release/v3.8.37 — x-omniroute-strip-reasoning header. Cherry-picked onto release tip (resolved chatCore.ts/headers.ts adjacency conflict, kept resolveCompressionHeader + isStripReasoningRequested); tests 8/8 green.

* fix(combo): flatten Anthropic tool messages + tool history to prevent upstream 503 (#4648)

Integrated into release/v3.8.37 — flattenToolHistory helper (combo anti-503). Cherry-picked onto release tip; tests 9/9 green.

* feat(headroom): proxy lifecycle management + dashboard UI (Docker sidecar supported) (#4649)

Integrated into release/v3.8.37 — headroom proxy lifecycle (status/start/stop, local-only + spawn-capable per Rules #15/#17). Cherry-picked onto release tip; lifecycle 7/7 + route-guard 43/43 + check:cycles green.

* feat(cli): multi-model support for Factory Droid CLI (#4682)

Integrated into release/v3.8.37 — Factory Droid multi-model support. Cherry-picked onto release tip (kept readJsoncConfig + droidCustomModels imports); droid-custom-models 11/11 green.

* fix(providers): require Default Model in compatible-provider API-key setup (#4641)

Integrated into release/v3.8.37 — require Default Model in compatible-provider API-key setup. Cherry-picked fix + test-move onto release tip (kept release providerSpecificData + QuotaScrapingFields; fixed moved-test import path; baseline rebaseline unneeded, 865<866); UI test 2/2 green.

* fix(dashboard): stop double-masking already-masked API key in list (E2E 3/9 regression) (#4671)

Integrated into release/v3.8.37 — render server-masked key verbatim (drop redundant maskKey call). Note: release's maskKey already guards '****' (since v3.8.34), so this is a safe simplification; added a contract test pinning the **** passthrough invariant (2/2 green, would fail against the pre-guard maskKey = the historical double-mask bug).

* chore(quality): rebaseline file-size for rc17 PR batch drift

Own growth from the merged rc17 PRs (#4678/#4686/#4688) at existing chokepoints —
cohesive, not extractable:
- open-sse/handlers/responseSanitizer.ts 1103->1122 (SanitizeOpenAIResponseOptions + stripReasoning, #4678)
- open-sse/services/tokenRefresh.ts 2070->2090 (codex 401 unrecoverable-refresh guard, #4686)
- tests/unit/token-refresh-service.test.ts 1322->1353 (401 regression case, #4686)
- tests/unit/translator-openai-responses-req.test.ts 1047->1050 (reasoning_effort assertion, #4688)

* docs(env): document HEADROOM_URL in .env.example + ENVIRONMENT.md

The headroom proxy lifecycle (#4649) reads HEADROOM_URL (src/lib/headroom/detect.ts,
default http://localhost:8787) but it was missing from the env contract, tripping
check:env-doc-sync. Adds the var to both .env.example (commented, has a default) and
the Proxy Health table in ENVIRONMENT.md.

* fix(sse): stream writer mock abort() returns a Promise (#4788)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(cli): fall back to default data dir when DATA_DIR is not writable (#4767)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(oauth): verify Cursor installation on Linux before auto-import (#4770)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): track Ollama streaming usage from raw NDJSON chunks (#4754)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): strip enumDescriptions from antigravity tool schema (#4740)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): include low-level cause details in formatProviderError (#4741)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(translator): strip x-anthropic-billing-header in claude-to-openai (#4728)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): gate Kiro image attachments behind a Claude-capability check (#4763)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): read Antigravity usage from the response.usageMetadata envelope (#4785)

Integrated into release/v3.8.37 — Antigravity response.usageMetadata envelope. Cherry-picked onto release tip (resolved test-tail adjacency with #4754 Ollama block); usage-extractor 23/23 green.

* fix(api): fall back to existing access token for any OAuth provider on refresh failure (#4786)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(cli): verify launchd registration + skip self-SIGTERM in macOS autostart (#4765)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(executors): anthropic-compatible-* gateways get Bearer alongside x-api-key (#4729)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): json_schema fallback for OpenAI-compatible providers (#4766)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): use workos auth token shape for cline (#4787)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* feat(sse): parse Gemini CLI 429 retryDelay from structured RetryInfo (#4738)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; tests green.

* fix(sse): finalize tool_calls finish_reason on early stream end in OpenAI Responses translator (#4764)

Integrated into release/v3.8.37 — computeFinishReason finalizes tool_calls on early stream end (Responses translator). Cherry-picked onto release tip; responses-translation-fixes 29/29 green.

* test(sse): golden-lock provider.ts translate-path across all providers (#4734)

Integrated into release/v3.8.37 — golden-lock for provider.ts translate-path. Cherry-picked onto release tip; snapshot regenerated against the current provider set (UPDATE_GOLDEN=1, 167 entries); golden test 3/3 deterministic.

* chore(quality): rebaseline file-size for rc17 leva2 PR batch drift

Own growth from the merged leva2 PRs (cohesive, not extractable):
- src/lib/usage/providerLimits.ts 950->955 (#4786)
- open-sse/executors/default.ts NEW frozen @828 (#4729 + #4766 + #4787 header branches)
- open-sse/translator/request/openai-to-kiro.ts 807->814 (#4763)
- open-sse/translator/response/openai-responses.ts 923->937 (#4764)
- tests/unit/executor-default-base.test.ts 1339->1440 (#4766)
- tests/unit/translator-openai-to-kiro.test.ts 918->980 (#4763)

* fix(dashboard): align Engine Combos editor engines with API schema (#4955) (#5062)

The named-combos pipeline dropdown offered four engines (headroom,
session-dedup, ccr, llmlingua) that stackedPipelineStepSchema rejects, so
selecting one made PUT /api/context/combos/[id] return HTTP 400 while
saveCombo swallowed the non-OK response (if (!res.ok) return). Editing the
default 'Standard Savings' combo and changing an engine reproduced the 400.

- Add canonical STACKED_PIPELINE_ENGINE_INTENSITIES next to the schema as the
  single source of truth; the client dropdown imports it so it can never drift
  from the discriminated union the API validates against.
- Surface save errors and empty-name/empty-pipeline validation in the editor
  instead of failing silently.
- Add a parity unit test asserting the UI engine map equals the schema union
  and that every (engine, intensity) the UI emits is accepted.

* fix(sse): filter nameless hosted tools when converting Responses API to Chat format (#4789)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(dashboard): keep desktop sidebar visible via explicit CSS class (#4812)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip enumDescriptions from Antigravity tool schemas (#4813)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(dashboard): resolve passthrough model aliases by providerId in ModelSelectModal (#4815)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(oauth): allow per-connection refresh lead-time override via providerSpecificData.refreshLeadMs (#4818)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip X-Stainless-* headers and normalize SDK User-Agent for OpenAI-compatible endpoints (#4820)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip Gemini built-in tools when functionDeclarations present in Antigravity envelope (#4821)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(api): surface a Docker-localhost hint on provider-node validation connection errors (#4822)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): resolve bare model names to connection defaultModel before upstream calls (#4825)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(build): trace-include sql.js sql-wasm.wasm in standalone bundle (#4839)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip Composer <|final|> sentinel markers leaking after Composer reasoning (#4842)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(config): sync full SiliconFlow model list into registry (#4844)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): close reasoning before message content in Responses stream (#4848)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): reject unsupported Kiro [1m] context suffix (#4816)

Integrated into release/v3.8.37 — cherry-picked onto release tip; test-tail conflict with #4763 resolved (kept both image + [1m] test blocks); CHANGELOG re-merged; 29/29 green.

* fix(db): validate HuggingFace tokens via whoami-v2 auth probe (#4819)

Integrated into release/v3.8.37 — defining commit re-homed onto the god-file-split validation module (validateHuggingFaceProvider in validation/openaiFormat.ts + map wiring); 115/115 green.

* fix(sse): make anthropic-version default-guard case-insensitive (#4823)

Integrated into release/v3.8.37 — conflict with #4729 Bearer-fallback resolved (kept both Bearer fallback + case-insensitive anthropic-version guard); 48/48 green.

* fix(sse): sanitize Kiro tool schemas to avoid 400 "Improperly formed request" (#4847)

Integrated into release/v3.8.37 — conflict in kiro-to-openai.ts resolved (kept release fallbackToolCallId + adopted #1375 toolNameMap remap); 7/7 green.

* feat(sse): add GPT-4 to the GitHub Copilot provider (#4798)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(sse): add GPT-4o mini to GitHub Copilot provider (#4797)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(api): add MiniMax-M3 pricing row (#4814)

Integrated into release/v3.8.37 — pricing row re-homed onto god-file-split pricing/regional.ts (pricing.ts is now a barrel); 4/4 green.

* fix(cli): save runtime deps with --save-exact so a sibling install can't prune them (#4841)

Integrated into release/v3.8.37 — trayRuntime conflict resolved (kept release SYSTRAY_SPEC + added --save-exact); 2/2 green.

* fix(sse): preserve required fields in antigravity tool schemas (#4843)

Integrated into release/v3.8.37 — conflict resolved (kept #4740/#4813 enumDescriptions strip + typed normalizeSchemaTypes, added required-preservation helpers; test-tail merged keeping both enumDescriptions + required tests); 7/7 green.

* chore(quality): rebaseline file-size for rc17b leva3 PR batch drift

* fix(sse): strip reasoning blobs from agentic context to prevent O(n^2) token growth (#4849)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): unwrap Qoder HTTP 200 SSE error envelope so fallback can trigger (#4850)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): strip temperature for Claude models with extended thinking (#4853)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): emit valid concatenable kiro tool_calls.arguments deltas (#4855)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(sse): add toggleable tool-source diagnostics (#4856)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): redact api key from the AUTH debug log in the chat handler (#4858)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): forward AI SDK image parts in Responses translator (#4859)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): resolve custom combos by id and case-insensitive name (#4446) (#4869)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): exclude WS bridge controller-closed error from provider breaker (#4602) (#4870)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(providers): add xAI Grok inbound translators and thinking patcher (#4910)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(embeddings): add dimensions override field to embedding combos (#4913)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* feat(oauth): Codex bulk-import endpoint — POST /api/oauth/codex/import (#4914)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(antigravity): retry transient upstream failures (#4941)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): surface malformed HTTP-200 upstream responses (#4942)

Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.

* fix(sse): normalize Codex custom tools (apply_patch) to { input: string } schema (#4862)

Integrated into release/v3.8.37 — conflict in request/openai-responses.ts resolved (kept #4789 nameless-tool skip + added #1007 custom-tool {input:string} normalization); 48/48 green incl. #4789/#4859 regression.

* fix(sse): dense, deterministic output ordering in Responses API response.completed (#4906)

Integrated into release/v3.8.37 — manual integration with #4862 in response/openai-responses.ts (custom-tool funcItem + dense recordCompletedItem). Fixed a latent #4848 interaction: the close-reasoning-before-message guard force-closed <think>-tag reasoning prematurely, which dense output (#4906) then snapshotted as a partial buffer ("plan" vs "planning") — scoped the guard to native reasoning_content (!inThinking) in BOTH transformer + translator paths. Full Responses suite 203/203 green incl. #4848/#4862 regression.

* feat(sse): auto-promote successful combo model to position #1 (#4852)

Integrated into release/v3.8.37 — dropped the stale file-size-baseline.json hunk (re-derived against the rc17b rebaseline); code+test applied clean; 13/13 green.

* feat(providers): add Pioneer AI (Fastino Labs) provider (#4909)

Integrated into release/v3.8.37 — providers.ts apikey block re-homed onto god-file-split src/shared/constants/providers/apikey/frontier-labs.ts (inline APIKEY_PROVIDERS no longer exists); registry/pioneer + providers/index.ts applied clean; 6/6 green.

* add DGrid AI gateway provider (#4931)

Integrated into release/v3.8.37 — rebased the contributor's commit onto the release tip; providers.ts god-file-split conflict resolved by relocating the dgrid APIKEY_PROVIDERS entry into apikey/gateways.ts; CHANGELOG added. 7/7 green. Thanks @dgridOP!

* chore(quality): rebaseline file-size for rc17b leva4 PR batch drift

* docs(routing): sync combo strategy docs for Fusion (17 strategies) (#5067)

Fusion (16th strategy, panel fan-out + judge synthesis) and headroom
shipped but the strategy-count docs were stale (14/15) and omitted both.
Update every combo-strategy reference to the canonical 17, add fusion +
headroom to all strategy tables, and add a dedicated Fusion section to
AUTO-COMBO.md documenting judgeModel / fusionTuning config + an example.

- CLAUDE.md, README.md, FEATURES.md, RESILIENCE_GUIDE.md,
  ARCHITECTURE.md, OPEN_SSE_ARCHITECTURE.md, OMNIROUTE_VS_ALTERNATIVES.md,
  docs/README.md, request-pipeline.mmd: 14/15 -> 17, list fusion + headroom
- docs/routing/AUTO-COMBO.md: strategy table + new Fusion strategy section
- docs/openapi.yaml: add reset-window, headroom, fusion to the strategy enum

* fix(oauth): classify /api/oauth/cursor/auto-import as local-only (route-guard) (#5070)

The Cursor auto-import route runs execFile("which", ["cursor"]) to verify a
local Cursor install before importing credentials — a child-process spawn. The
check:route-guard-membership gate (Hard Rules #15/#17) flagged it as an
unclassified spawn-capable route: reachable past the loopback gate, an
RCE-via-tunnel surface (a leaked JWT over a tunnel could trigger the spawn).

Classify the specific path in LOCAL_ONLY_API_PREFIXES so loopback enforcement
runs unconditionally before any auth check. Scoped to the exact path — the rest
of /api/oauth/ (browser redirect/callback flows) stays remote-reachable.

TDD: added a failing-then-passing assertion in route-guard-local-prefix.test.ts
(classification + an over-broadening guard proving sibling OAuth paths stay
remote). check:route-guard-membership now reports 0 new gaps.

* chore(release): v3.8.37 — 2026-06-26

---------

Co-authored-by: dgridOP <dgrid_op@outlook.com>
2026-06-26 02:51:06 -03:00
Diego Rodrigues de Sa e Souza
a7ae9550bd Release v3.8.36 (#4854)
* chore(release): open v3.8.36 development cycle

* refactor(chatCore): extrai resolveCompressionSettings (#3501) (#4826)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 1/13)

* refactor(chatCore): extrai predicados puros de combo de compressão (#3501) (#4824)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 2/13)

* refactor(chatCore): extrai emitOutputStyleTelemetry (#3501) (#4811)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 3/13)

* refactor(chatCore): extrai writeCompressionAnalytics (bloco analytics completo, #3501) (#4817)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 4/13)

* refactor(chatCore): extrai runPluginOnRequestHook (#3501) (#4827)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 5/13)

* refactor(chatCore): extrai applyClientUsageBuffer (buffer/estimate de usage non-streaming, #3501) (#4832)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 6/13)

* refactor(chatCore): extrai buildPostCallGuardrailContext (contexto guardrail post-call, #3501) (#4831)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 7/13)

* refactor(chatCore): extrai storeSemanticCacheResponse (cache-store non-streaming, #3501) (#4828)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 8/13)

* refactor(chatCore): extrai buildNonStreamingResponseHeaders (headers de resposta non-streaming, #3501) (#4835)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 9/13)

* refactor(chatCore): extrai maybeConvertJsonBodyToSse (#3089 JSON→SSE streaming, #3501) (#4833)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 10/13)

* refactor(chatCore): extrai assembleStreamingResponseHeaders (headers de resposta streaming, #3501) (#4836)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 11/13)

* refactor(chatCore): extrai storeStreamingSemanticCacheResponse (cache-store streaming, #3501) (#4829)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 12/13)

* refactor(chatCore): extrai assembleStreamingPipeline (chain de transforms streaming, #3501) (#4837)

Integrated into release/v3.8.36 (#3501 chatCore extraction stack 13/13)

* ci(quality): shift heavy validations to the PR→release fast-path (release-acceleration) (#4857)

* feat(quality): add check:test-runner-api gate (vitest-only dirs must use vitest API)

* feat(release): reusable CHANGELOG i18n-mirror sync script

* chore(ops): add prune-stale-worktrees.sh (dry-run by default)

* ci(quality): run test-runner-api + docs-all + vitest + full unit suite on PR->release fast-path

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(quota): cota exclusiva lista qtSd/ no /v1/models (#4806) + limite EPSILON não bloqueia (#4830)

Integrated into release/v3.8.36 — quota-exclusive qtSd/ listing (#4806) + EPSILON placeholder no longer blocks; rebuilt from stale base (3 defining commits cherry-picked clean over release tip)

* feat(sse): add Google Flow video-generation provider (#4569) (#4769)

Integrated into release/v3.8.36 — Google Flow video-generation provider (#4569), release-green validated (typecheck + 21 tests + file-size)

* fix(api): auth on compression run-telemetry + document OMNIROUTE_EVAL_CREDENTIALS (#4694, #4720) (#4796)

Integrated into release/v3.8.36 — auth on compression run-telemetry + OMNIROUTE_EVAL_CREDENTIALS doc, release-green validated (typecheck + 3 tests + env-doc-sync)

* fix(translator): strip top-level client_metadata on the OpenAI passthrough (port from 9router#1157) (#4624)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize `developer` role to `system` for OpenAI-format providers (#4625)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): emit </think> close marker for Anthropic thinking blocks (#4633)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(translator): normalize tools to Anthropic-native shape for non-Anthropic providers (#4650)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(gemini): preserve `pattern` in antigravity tool schema sanitizer (#4651)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(perplexity): validate API keys via /v1/models endpoint (#4654)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(image): prevent compatible nodes from shadowing provider aliases (#4656)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(cli-tools): tolerate JSONC (comments, trailing commas) in tool settings (#4659)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)

* fix(security): validate kiro region to prevent SSRF (GHSA-6mwv-4mrm-5p3m) (#4629)

Integrated into release/v3.8.36 — kiro region SSRF guard (GHSA-6mwv-4mrm-5p3m), port rebuilt clean over release tip

* fix(cli): harden the systray2 tray runtime (port of 9router#1080) (#4628)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(test): validate anthropic-compatible connections via POST /v1/messages (#4657)

Integrated into release/v3.8.36 — anthropic-compat validation via POST /v1/messages (port 584cf66a), rebuilt clean + baseline; release-green

* fix(executors): strip params unsupported by the target provider/model (#4658)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* fix(claude-oauth): respect 429 backoff on usage endpoint to reduce spam (#4655)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* feat(api/v1): include alias-backed models in /v1/models listing (#4630)

Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated

* chore(quality): rebaseline catalog.ts 1574->1577 (#4630 aliases sobre quota-exclusive da release) (#4879)

rebaseline

* feat(compression): Kiro/CodeWhisperer tool-result compression engine (#4635)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(security): don't trust loopback socket as local when behind reverse proxy (#4632)

Integrated into release/v3.8.36 — port rebuilt clean, release-green

* fix(opencode): preserve DeepSeek reasoning content in streamed responses (#4631)

Integrated into release/v3.8.36 — DeepSeek reasoning_content injection (port #1099); release-green

* fix(copilot,antigravity): cap maxOutputTokens at 16384 to stop "Invalid Argument" 400 (#4636)

Integrated into release/v3.8.36 — cap maxOutputTokens 16384 antigravity (port #779); release-green

* fix(dashboard): show custom vision models in LLM selector (#4653)

Integrated into release/v3.8.36 — custom vision models in LLM selector (port 5e5e78d3); release-green

* fix(claude): omit adaptive thinking + output_config.effort for haiku (#4661)

Integrated into release/v3.8.36 — haiku adaptive-thinking omit (port); release-green

* feat(provider): CodeBuddy CN (copilot.tencent.com) — full stack (#4664)

Integrated into release/v3.8.36 — CodeBuddy CN provider (port efd20be8); usage.ts import + public-creds allowlist line reconciled; release-green

* feat(combo): Fusion strategy — parallel panel + judge synthesis (16th strategy) (#4652)

Integrated into release/v3.8.36 — Fusion combo strategy (16th, port 87e5c1c6); combo.ts baseline reconciled; release-green

* feat(proxy-pool): Deno Deploy relays + group action buttons (#4643)

Integrated into release/v3.8.36 — Deno Deploy relays (port #1437); proxies.ts baseline reconciled + env docs restored; release-green

* fix(security): pin image fetch DNS resolution to prevent SSRF rebinding (GHSA-cmhj-wh2f-9cgx) (#4634)

Integrated into release/v3.8.36 — pin DNS for image fetch SSRF rebinding guard (GHSA-cmhj-wh2f-9cgx, port c7d07448); caller DNS stubs + test-file baseline reconciled; release-green

* fix(github): route Copilot Codex models to /responses (port from 9router#102) (#4626)

Integrated into release/v3.8.36 — route Copilot Codex models to /responses (port #102); release-green

* fix(copilot): never route Gemini/Claude variants to /responses (chat-completions only) (#4627)

Integrated into release/v3.8.36 — never route Gemini/Claude to /responses (port #1536); fused with #4626 codex routing via supportsResponsesEndpoint gate; release-green

* docs(ops): add canonical incident response runbook (#4868)

Integrated into release/v3.8.36

* docs(perf): add per-endpoint p50/p95/p99 latency + cost budgets (#4867)

Integrated into release/v3.8.36

* fix(proxy): fan out direct dispatcher streams (#4803)

Integrated into release/v3.8.36

* fix(antigravity): exclude standard Gemini rate limit message from quota exhaustion keywords (#4810)

Integrated into release/v3.8.36

* fix(sse): skip third-party tool-name cloak for Anthropic server tools (#4808)

Integrated into release/v3.8.36

* fix(install): make transformers optional for CUDA-host installs (#4807)

Integrated into release/v3.8.36

* fix(combo): propagate selected connection ID to fallback error responses for correct model lockout (#4809)

Integrated into release/v3.8.36

* fix db storage tuning settings (#4834)

Integrated into release/v3.8.36

* fix(sse): drop ccp pin when pinned provider is durably unhealthy (failover + anti-flap) (#4864)

Integrated into release/v3.8.36

* fix(claude): skip mcp__ tool-name cloak + guard missing connectionId (#4861)

Integrated into release/v3.8.36

* chore(quality): reconcile env-doc + file-size base-reds in release/v3.8.36 (#4886)

- env-doc-sync: document PIN_DROP_BACKOFF_LEVEL / PIN_DROP_GRACE_MS (added by the
  ccp-pin health gate #4864) in .env.example + ENVIRONMENT.md.
- file-size: rebaseline image-generation-handler.test.ts 1996 -> 2019 to its actual
  size (pre-existing drift).

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(codex): drop non-standard codex.* events that break responses.stream (env-gated, #4602) (#4715)

Integrated into release/v3.8.36

* feat(routing): honor X-Route-Model header to override body.model (#4863)

Integrated into release/v3.8.36

* feat(live-ws): allow non-loopback clients via LIVE_WS_ALLOWED_HOSTS (closes #4873) (#4877)

Integrated into release/v3.8.36 (live-ws + combo-api commits; Tailscale CGNAT commit held pending opt-in/opt-out decision)

* chore(claude,codex): bump pinned CLI identity — Claude 2.1.158→2.1.187, Codex 0.132.0→0.142.0 (#4883)

Integrated into release/v3.8.36

* fix(security): SSRF allowlist bypass via x-relay-path nos relays Deno/Vercel (#4899)

Integrated into release/v3.8.36

* feat(quota): recuperação proativa de conexões em cooldown (cron heal) [Fase 3 #8] (#4900)

Integrated into release/v3.8.36

* fix(quota): policy inválida não vaza allow + guard connectionIds vazio [Fase 3 #10] (#4901)

Integrated into release/v3.8.36

* feat(quota): saturação real do Claude no fair-share via /api/oauth/usage (#4885)

Integrated into release/v3.8.36

* chore(dashboard): rename Qoder display label from "Qoder AI" to "Qoder" (#4733)

Integrated into release/v3.8.36

* fix(ci): include coverage/lcov.info in coverage-report artifact for SonarQube (#4670)

Integrated into release/v3.8.36

* fix(cli): bump better-sqlite3 runtime pin to 12.10.1 for Node 26 (#4685)

Integrated into release/v3.8.36

* docs: clarify Kiro is ~50 credits/month per account, not unlimited (#4690)

Integrated into release/v3.8.36

* docs(agentbridge): document Electron NODE_EXTRA_CA_CERTS, real model IDs, identity caveat (#4718)

Integrated into release/v3.8.36

* docs(ops): document the release-green family (green-prs, check:release-green, babysit, nightly) (#4679)

Integrated into release/v3.8.36

* fix(translator): replay reasoning_content on plain Xiaomi MiMo turns (port from 9router#1321) (#4639)

Integrated into release/v3.8.36

* feat(opencode-go): advertise glm-5.2 and kimi-k2.7-code (align with official Go endpoints) (#4711)

Integrated into release/v3.8.36

* feat(db): track API endpoint dimension on usage_history (#4676)

Integrated into release/v3.8.36 (migration renumbered 103→105; endpoint plumbed through extracted usage-stats helpers)

* fix(cli): SIGKILL systray child PID before IPC close to avoid macOS NSStatusItem orphan (#4732)

Integrated into release/v3.8.36

* feat(proxy-pool): Cloudflare Workers proxy deployer + pool integration (#4640)

Integrated into release/v3.8.36 (relay type added to RELAY_TYPES set; dropdown UX preserved + Cloudflare item added; proxies.ts file-size rebaselined 1057→1060)

* chore(quality): conserta base-red de release/v3.8.36 (gates + 7 testes + build MDX) (#4915)

A base tinha base-red sistêmica herdada de PRs de outras sessões, bloqueando
TODOS os PRs do ciclo (o TIA roda a suíte full em fail-safe p/ diffs hub).

4 Fast Quality Gates:
- test-discovery (#4877): live-server-allowlist.test.ts em tests/unit/server/
  (não-coletado) + vitest → nunca rodava. Convertido p/ node:test em tests/unit/security/.
- any-budget:t11 (#4664): 3 explicit-any em tokenRefresh.ts tipados (sem crescer file-size).
- docs-symbols (#4868): rotas inexistentes → /api/system/version e
  PUT /api/providers/{id} {isActive:false}.
- docs-all fabricated-claim (#4868 + #4718): 5 bin/*.sh reais criados (rollback,
  snapshot-data, restore-data, restore-policies, cold-start-bench) + _ops-common.sh
  (snapshot VACUUM INTO, guards de confirmação/TTY, testes de contrato); NODE_EXTRA_CA_CERTS
  (env de runtime Node) na allowlist do checker.

7 testes unit base-red (de features alheias à quota):
- oauth-providers-config (#4664): teste alinhado ao provider codebuddy-cn do registry.
- antigravity-model-aliases (#4636): maxOutputTokens esperado 32769→16384 (cap intencional).
- provider-request-capture #4091 (#4861): exemplo do teste trocado de mcp__ (que #4861
  isenta de cloak por causa dos 400s de assimetria de histórico) para um tool de terceiro
  cloakável — preserva o invariante de #4091 SEM reverter #4861.
- combo-error-response: convertido de vitest p/ node:test (era coletado pelo glob node:test
  e crashava); api/** e server/** removidos do vitest.config (config morta).

Build MDX (dast-smoke, #4679):
- docs/ops/RELEASE_GREEN.md não tinha frontmatter `title` → fumadocs-mdx rejeitava no
  webpack compile ("invalid frontmatter: title expected string"), quebrando o next build
  (e o deploy). Frontmatter title adicionado (único doc do collection sem ele).

17/17 Fast Quality Gates + suíte unit completa (17737 testes, 0 fail) + vitest verdes localmente.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): saturação proativa por headers de tokens (universal) [Fase 3 #2] (#4907)

storeRateLimitHeaders só capturava os headers de REQUESTS (RPM/min), que não
refletem a pressão de TOKENS. Agora também parseia os headers de tokens (em toda
resposta, sucesso também) para throttle proativo antes do 429:
- Anthropic: anthropic-ratelimit-tokens-{limit,remaining,reset} (+ input/output), RFC3339.
- OpenAI: x-ratelimit-{limit,remaining,reset}-tokens, reset em duração (6m0s).

saturation = 1 − remaining/limit; resetAt normalizado a epoch (parse de duração
ReDoS-safe). getTokenHeaderSaturation por (provider, connectionId). fetchGeneric-
Saturation passa a usar esse sinal (complementa o oauth/usage do #1, que segue
primário p/ Claude). Fail-open, cache mantido, request-path inalterado.

16 testes novos + regressão (oauth/usage #1 8/8, signals 6/6) = 30/30;
typecheck:core + eslint limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): estratégia de combo "headroom" — seleção por folga de cota [Fase 3 #4] (#4908)

Nova estratégia de roteamento que escolhe a conexão com MAIS folga de plano:
headroom = 1 − max(util_5h, util_7d) (técnica do dario), via getSaturation
(melhorado p/ Claude no #1). Proativo em vez de só fill-first reativo.

- Helper PURO headroomRanking.ts (computeHeadroom + rankByHeadroom; saturação
  injetada, não-mutante, tie-break estável, fail-open).
- Orderer async em combo/quotaStrategies.ts (reusa a maquinaria reset-aware de
  expansão de conexões + concorrência limitada; seam injetável).
- Registrada como "headroom" em routingStrategies (combo-only); fill-first segue
  default — nenhuma estratégia existente tocada.
- baseline file-size combo.ts 3168->3180 (só +12L de dispatch; lógica fora do god-file).

16 testes novos + combo-strategies 15/15 = 31/31; typecheck:core + eslint +
file-size limpos.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): cap per-(key,model) — quota_allocation_model_caps [Fase 3 #7] (#4927)

* feat(quota): cap per-(key,model) com tabela quota_allocation_model_caps [Fase 3 #7]

Fecha o buraco onde uma API key pode drenar o pool inteiro consumindo um único modelo.

Tabela nova: quota_allocation_model_caps(pool_id, api_key_id, model, cap_value, cap_unit)
PK composta (pool_id, api_key_id, model). cap_unit alinhado ao QuotaUnit existente.

Comportamento: keyA acima do cap para modelo M → bloqueada somente em M; ainda
permitida em qualquer outro modelo no mesmo pool. Cap <= EPSILON → ignorado (seed).

Consumo por-(key,model) usa bucket segregado no quota_consumption existente
(poolId mangled ':model:<model>') com window fixa 'hourly'; nenhuma nova tabela
ou método de store necessário.

Módulo novo: src/lib/db/quotaModelCaps.ts (getModelCap/setModelCap/deleteModelCap/listModelCaps)
enforce.ts ganha o pre-check em enforceQuotaShare + recording em recordConsumption.
EnforceInput e RecordConsumptionInput ganham model?: string (backward-compatible).
localDb.ts re-exporta os 4 helpers (Hard Rule #2).

TDD: tests/unit/quota-per-key-model.test.ts — 4 cenários (bloqueia em M, permite em M2,
sem cap → sem bloqueio, EPSILON → ignorado). Todos os gates de qualidade passam.

* feat(quota): plumba model resolvido no hot path para ativar o per-(key,model) cap [Fase 3 #7]

A tabela/enforce do commit anterior estavam INERTES: o hot path não passava `model`
ao enforce nem ao record, então nenhum model-cap disparava em produção.

Plumbagem (model resolvido = mesma var usada no log/roteamento, pós background-redirect/alias):
- chatCore.ts: enforceQuotaShare ganha `model`; scheduleQuotaShareConsumption recebe `model`.
- chatCore/quotaShareConsumption.ts: threade `model` no RecordConsumptionInput (non-streaming).
- spendRecorder.ts: recordStreamingConsumption já recebia `model` — agora o coloca no
  RecordConsumptionInput (streaming accrue por-modelo).
- embeddings.ts: enforce + record ganham `model`.

Namespace do cap = id do modelo RESOLVIDO (o mesmo de modelForScope/pendingScope/getUnsupportedParams),
não o requestedModel cru nem o finalModelToUpstream (sem prefixo de provider). Operador configura
o cap contra esse id. `model || undefined` em todos os pontos: vazio/null → check pulado (fail-safe,
zero latência — só um campo no objeto).

Teste de integração novo (tests/unit/quota-per-key-model-hotpath.test.ts): prova end-to-end que
N consumos via scheduleQuotaShareConsumption({model}) → enforceQuotaShare({model}) bloqueia, e que
outro modelo no mesmo pool ainda passa; + guard de que enforce SEM model nunca dispara model-cap.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5] (#4929)

* feat(quota): session stickiness p/ integridade de prompt-cache [Fase 3 #5]

Adiciona stickiness de sessão ao roteamento de combo: uma conversa multi-turno
é roteada para a MESMA conexão enquanto ela permanecer saudável, evitando a
perda do prompt-cache do provider (custo 5-10× sem stickiness, efeito conhecido
no dario/clewdr).

Implementação:
- `open-sse/services/combo/sessionStickiness.ts` (novo, <800 linhas):
  mapa em memória (messageHash → connectionId) com TTL 15 min + cap 500 entradas;
  `applySessionStickiness` promove a conexão sticky ao índice 0 dos targets
  ordenados pelo strategy, guardado por `computeHeadroom > 0.15` (threshold);
  quando saturada (headroom ≤ 0.15), o binding é limpo e a seleção normal reage.
  Hash da sessão = SHA-256 dos primeiros chars da 1ª mensagem user → 16 hex chars.
  Seam de teste: `__setStickinessHeadroomFetcherForTests`.
- `open-sse/services/combo.ts`: import + 2 pontos de integração (pré-eval-scores
  e pós-success), dentro do orçamento congelado de 3180 linhas.
- `tests/unit/combo-session-stickiness.test.ts`: 19 testes node:test + assert/strict,
  todos via injeção de fetcher (zero rede/DB).

Threshold 0.15: conexão a >85% de utilização está a um burst de rate-limit;
o benefício de cache não compensa manter-se numa conexão degradada. Valor
alinhado com a zona de soft-penalty do restante do engine de quota-share.

* test(combo): isola combo-strategies da session stickiness (#5)

selectedConnectionFor reusa o mesmo body, então o sticky map (#5) fixava a
connection após a 1ª chamada e quebrava o round-robin tie-break do teste
reset-aware. Limpa o sticky map no início da helper — a stickiness tem suíte
própria (combo-session-stickiness). Sem enfraquecer asserts.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): buckets multi-janela por conexão (5h/7d/per-model) [Fase 3 #3] (#4928)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(providers): decompõe catálogo providers.ts em módulos de dados (godfile sweep, #3501) (#4917)

Integrado em release/v3.8.36 (godfile sweep providers.ts, #3501)

* refactor(pricing): decompõe pricing.ts em shared-tiers + DEFAULT_PRICING particionado (godfile sweep, #3501) (#4918)

Integrado em release/v3.8.36 (godfile sweep pricing.ts, #3501)

* refactor(api): extrai camada-folha pura de validation.ts (URL/headers/transport) (#4921)

Integrado em release/v3.8.36 (validation.ts split fatia 1 — leaf layer)

* refactor(api): extrai validators web-cookie + Meta AI de validation.ts (#4922)

Integrado em release/v3.8.36 (validation.ts split fatia 2 — web-cookie + Meta AI)

* refactor(api): extrai validators enterprise-cloud + probe compartilhado de validation.ts (#4923)

Integrado em release/v3.8.36 (validation.ts split fatia 3 — enterprise-cloud + probe)

* refactor(api): extrai validators áudio/speech + misc apikey de validation.ts (#4930)

Integrado em release/v3.8.36 (validation.ts split fatia 4 — áudio/speech + misc apikey)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9] (#4939)

* feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight + gating per-model) [Fase 3 #9]

Estratégia interna "quota-share" isolada num módulo dedicado — NÃO toca a seleção/
fair-share genérica (decisão do dono: não mexer no que já funciona). Os combos qtSd/
(quotaCombos.ts) passam de fill-first para essa strategy; combo.ts ganha só 1 branch
de dispatch que delega 100% ao módulo (nenhum case existente alterado).

- quotaShareStrategy.ts: gating per-model (isBucketSaturated do #3) + DRR (quantum
  proporcional ao weight) + P2C sobre carga in-flight.
- quotaShareInflight.ts: contador in-flight com TTL/lease de 120s — fallback do
  decrement-on-abort sem precisar instrumentar o combo genérico.
- "quota-share" registrada como strategy INTERNA (não exposta na UI).
- testes de síntese (quota-combo-balancing, quota-multiprovider) alinhados: a strategy
  esperada dos combos qtSd/ passa de "fill-first" para "quota-share" (alinhamento ao novo
  comportamento intencional, não mascaramento — os 73 testes de qtSd/ seguem verdes).

* test(quota-share): alinha 2 scope-guards ao godfile sweep (base-reds que bloqueavam o CI)

Dois testes de "arquivo contém X" quebraram por decomposições de godfile que outras
sessões mergearam no release DURANTE a validação de #9 — NÃO são regressão de #9
(que não toca validation/oauth). Alinhados ao novo layout, asserts preservados:
- proxy-bypass-scope-guard #3226: bypassProxyPatch foi extraído de validation.ts para
  validation/headers.ts (split #4921–#4930) → o teste lê a camada de validação.
- sse-error-passthrough #3324: a windsurf authHint foi extraída de providers.ts para
  providers/oauth.ts → o teste lê o novo local.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* refactor(api): extrai validators search + embedding/rerank de validation.ts (#4932)

Integrated into release/v3.8.36

* refactor(api): extrai format-validators (OpenAI/Anthropic) de validation.ts (#4933)

Integrated into release/v3.8.36

* refactor(db): extrai model-permission matching de db/apiKeys.ts (#4936)

Integrated into release/v3.8.36

* refactor(db): extrai row-parsers + tipos compartilhados de db/apiKeys.ts (#4943)

Integrated into release/v3.8.36

* refactor(db): extrai column-mapping (snake↔camel) de db/core.ts (#4947)

Integrated into release/v3.8.36

* refactor(db): extrai schema-column reconciliation de db/core.ts (#4948)

Integrated into release/v3.8.36

* refactor(sse): extrai scalar/format helpers de services/usage.ts (#4949)

Integrated into release/v3.8.36

* refactor(sse): extrai quota-core (UsageQuota + builders) de services/usage.ts (#4950)

Integrated into release/v3.8.36

* fix(translator): regroup parallel tool results adjacent to their assistant (#4714) (#4882)

Integrated into release/v3.8.36 (fixes #4714)

* fix(qoder): exchange PAT for jt-* job token before Cosy chat (#4683) (#4884)

Integrated into release/v3.8.36 (fixes #4683)

* refactor(sse): dedup fallback tool_call id helper (#4736)

Integrated into release/v3.8.36

* refactor(open-sse): extract safeParseJSON util, dedup tryParseJSON (#4735)

Integrated into release/v3.8.36

* fix(compression): eliminate ReDoS in math_inline preservation pattern (#4795) (#4838)

Integrated into release/v3.8.36 (fixes #4795)

* fix(combo): fetch models dynamically from custom provider endpoints (#4860)

Integrated into release/v3.8.36

* feat(providers): update volcengine-ark model list with DeepSeek V4 (#4905)

Integrated into release/v3.8.36

* fix(translator): provider thinking compatibility (DeepSeek/Gemini) (#4946)

Integrated into release/v3.8.36

* feat(combo): task-aware routing strategy (#4945)

Integrated into release/v3.8.36

* refactor(sse): extrai a família MiniMax de services/usage.ts (#4952)

Integrated into release/v3.8.36

* refactor(sse): extrai a família GLM de services/usage.ts (#4953)

Integrated into release/v3.8.36

* refactor(sse): extrai a família Antigravity de services/usage.ts (#4956)

Integrated into release/v3.8.36

* fix(dashboard): show custom provider given-name instead of internal id across dashboard pages (#4603) (#4960)

Integrated into release/v3.8.36 (fixes #4603)

* fix(api): evict stale in-memory rate-limit windows to stop slow heap leak (#4041) (#4957)

Integrated into release/v3.8.36 (fixes #4041)

* fix(api): parse /v1/responses body once instead of 3-4x on the hot path (#4041) (#4958)

Integrated into release/v3.8.36 (fixes #4041)

* fix(translator): preserve legitimate empty-string tool arguments in openai-to-claude streaming (#4951) (#4959)

Integrated into release/v3.8.36 (fixes #4951)

* chore(quality): reconcile file-size baseline for #4960 provider-display-name (#4961)

Integrated into release/v3.8.36

* fix(dashboard): restore home provider-topology card hidden by #4596 default (#4963)

Integrated into release/v3.8.36 — restores home topology card (#4596 regression)

* fix(build): drop @omniroute/open-sse from optimizePackageImports (build OOM) (#4968)

Integrated into release/v3.8.36 — fixes build OOM (optimizePackageImports open-sse)

* fix(quota): migração 107 ativa estratégia quota-share nos combos qtSd/ existentes [Fase 3 #9] (#4962)

Integrated into release/v3.8.36

* feat(quota): respeita max_concurrent por conexão no roteamento (#4965)

Integrated into release/v3.8.36

* feat(quota): combo quota-share espera cooldown curto e re-despacha (Variante A) (#4967)

Integrated into release/v3.8.36

* fix(quality): resolve base-reds da release — db-rules allowlist + task-aware router precedence (#4973)

Dois base-reds pré-existentes que reprovavam o CI da release v3.8.36 (Fast
Quality Gates + Unit Tests fast-path), independentes de qualquer feature em voo:

1. check:db-rules / allowlist: os módulos db-internal caseMapping (#4947) e
   schemaColumns (#4948), extraídos de db/core.ts e importados só por ele, não
   estavam em INTENTIONALLY_INTERNAL. Registrados na allowlist (correção
   canônica — são internos legítimos, não re-exportados pelo localDb).

2. auto-strategy honra LKGP/cost (combo-routing-engine.test.ts, 2 testes): o
   task-aware reordering (#4945, reorderByTaskWeight) roda para strategy "auto" e
   era aplicado DEPOIS do router explícito (selectWithStrategy: lkgp/cost),
   sobrescrevendo o orderedTargets[0] que o operador escolheu. Instrumentação
   provou: post-filter [0]=claude (LKGP) → post-task [0]=gpt-oss. Correção: quando
   o auto usa router explícito, preserva o [0] dele e deixa o task-aware refinar
   só a cauda de fallback. gpt-oss-120b PERMANECE tool-capable (não é mudança de
   catálogo; o model-capabilities-registry test segue verde).

Validado: 121 testes (combo-routing-engine + combo-task-aware + registry) verdes,
red-check confirmado, db-rules/file-size/typecheck/lint/prettier OK.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(quota): serializa concorrência por conexão no caminho quota-share (FASE 2.1) (#4970)

O gating de quota-share em selectQuotaShareTarget é fail-open: uma conexão
at-cap só é despriorizada, nunca bloqueada. Com 1 conexão por conta de
assinatura (caso comum), chamadas concorrentes ainda floodam a conta (→ 429 +
cooldown) — provado live na .15: 3 chamadas concorrentes com max_concurrent=1
despacharam todas em 94ms.

Adiciona um semáforo POR CONEXÃO em torno do dispatch quota-share: chamadas
excedentes esperam na fila em vez de floodar (key qsconn:<connectionId>, cap =
max_concurrent da conexão). Fail-open em fila saturada/timeout para nunca
piorar disponibilidade. Gated por strategy===quota-share + kill-switch
resilienceSettings.quotaShareConcurrencyLimit (default on; UI no ResilienceTab).

Lógica extraível isolada no leaf puro combo/quotaShareConcurrency.ts
(unit-testado: estabilidade da key, no-op sem cap, serialização real,
fail-open). Settings + schema + UI espelham comboCooldownWait.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* docs(resilience): document Quota-Share Concurrency Control (max_concurrent + serialization + cooldown-wait) (#4980)

Documents the v3.8.36 quota-share concurrency layers in RESILIENCE_GUIDE.md:
per-connection max_concurrent cap, the quota-share request serialization semaphore
(FASE 2.1, qsconn:<connectionId>, fail-open, kill-switch), and the combo
cooldown-aware retry — so operators know how to cap a subscription account's
concurrency and why the routing gate alone cannot contain a single-connection flood.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): proxy-pool success gating, sync timestamp, opt-in Redis (#4878) (#4988)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976) (#4986)

* fix(sse): fail over on 400 responses carrying rate-limit text (#4976)

* chore(quality): rebaseline accountFallback.ts file-size for #4976 fix

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(compression): stop RTK over-truncating file-read tool results (#4559) (#4987)

* fix(compression): stop RTK over-truncating file-read tool results (#4559)

* chore(quality): trim #4559 comment to keep rtk/index.ts within size cap

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954) (#4989)

* fix(sse): honor per-account proxies and fingerprint rotation in opencode executor (#4954)

* chore(quality): rebaseline auth.ts file-size for #4954 (+39: synthetic no-auth providerSpecificData hydration of fingerprints/accountProxies; irreducible credential-path wiring, covered by opencode-proxy-rotation-4954.test.ts + 159 auth/noauth regression)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540) (#4990)

* fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4540)

* chore(quality): document STATUS_SOFT_DEPRIORITIZE_FACTOR + rebaseline combo.ts for #4540

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): switch to visible filter after auto-hiding failed models in test-all (#4887) (#4991)

* fix(dashboard): switch to visible filter after auto-hiding failed models in OAuth provider test-all (#4887)

* test(dashboard): move #4887 test into tests/unit/ui so a CI runner collects it

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(pollinations): only enable jsonMode when JSON output is requested (#3981) (#5009)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003) (#5008)

* fix(antigravity): default safetySettings to all-OFF for parity with native Gemini paths (#5003)

* docs(changelog): restore #3981 pollinations entry eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665) (#5010)

* fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to prevent silent model substitution (#4665)

MODEL_MAP was missing the advertised catalog ids gpt-5.5, gpt-5.5-pro,
gpt-5.4-pro and gpt-5.2-pro, so MODEL_MAP[model] ?? model sent the dot-form
id verbatim to the ChatGPT backend-api, which silently rejected it and served
the default Plus model. Map each to its dash-form slug. gpt-4-5 is already
dash-form and falls through correctly, so it is intentionally left unmapped.

Extends the executor MODEL_MAP test with the four ids and adds a drift guard
asserting every advertised dot-form catalog id reaches the backend in dash-form
(never verbatim), guarding future catalog<->map drift.

file-size: tests/unit/chatgpt-web.test.ts frozen baseline 2809->2855 (+46) for
the added test cases and drift-guard test; executor source unchanged in baseline.

* docs(changelog): restore #3981/#5003 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005) (#5011)

* feat(combos): add editable per-combo description field persisted via /api/combos (#5005)

* docs(changelog): restore #3981/#5003/#4665 entries eaten by merge

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* Fix Ollama Cloud max reasoning effort (#4993)

Integrated into release/v3.8.36

* fix(copilot): replace execSync with execFile to prevent command injection (#5024)

Integrated into release/v3.8.36

* fix(plugin): auth.json dual-key fallback for auto-prefix migration (#5027)

Integrated into release/v3.8.36

* feat(endpoint): per-endpoint custom system prompt injection (#5022)

Integrated into release/v3.8.36

* fix(headroom): translate openai-responses input through OpenAI for compression (#5023)

Integrated into release/v3.8.36

* docs(changelog): add entries for #4993, #5024, #5027 (release notes credit)

* fix(api): stop /api/system/env/repair 500 on packaged install (#5006) (#5028)

* fix(api): stop /api/system/env/repair 500 on packaged install — lazy createRequire in sync-env.mjs (#5006)

scripts/dev/sync-env.mjs ran createRequire(import.meta.url) at module
top-level. When webpack bundles it into the standalone env-repair route,
import.meta.url is frozen to the build-machine path (file:///home/runner/...)
and createRequire throws during module evaluation, so the whole route
module fails to load and every GET returns HTTP 500 — breaking the
onboarding wizard on packaged/global installs.

- Move createRequire into the guarded better-sqlite3 block (only place
  that needs it); a bad import.meta.url now returns the safe default.
- resolveRootDir() falls back to process.cwd() when fileURLToPath throws.
- route.ts passes an explicit rootDir (process.cwd()) so the helper never
  derives the root from the frozen import.meta.url, matching the .env
  target used by createEnvBackup().
- Regression guard: assert sync-env.mjs has no top-level createRequire +
  getEnvSyncPlan(oauth) works with explicit rootDir without throwing.

* docs(changelog): restore #4993/#5023/#5024/#5027 + custom-system-prompt/headroom entries eaten by release merge

* chore(quality): rebaseline 3 inherited base-reds from release merge

Files NOT touched by this PR — grew on release/v3.8.36 via --admin merges and
inherited here through 'git merge origin/release':
- open-sse/executors/base.ts 1414->1416 (#4993 Ollama Cloud max-effort)
- src/lib/db/settings.ts 1149->1151 (#5023 custom system prompt)
- src/app/(dashboard)/.../endpoint/EndpointPageClient.tsx 2570->2612 (custom system prompt UI)

* chore(release): finalize v3.8.36 CHANGELOG + docs (2026-06-25)

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Makcim Ivanov <makcimbx@gmail.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: Jefferson Felizardo <jeffer1312@gmail.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
2026-06-25 13:17:40 -03:00
Diego Rodrigues de Sa e Souza
ee24eb52d4 Release v3.8.33 (#4515)
Release v3.8.33 — full CHANGELOG in the PR body. Blocking gates green (Build, Lint, Unit Tests 8/8, Package Artifact, Quality Gates, Quality Ratchet, Docs Sync, PR Test Policy, test-vitest). Admin-merged over a Node 26 future-compat timer flake (1/4) + an E2E UI flake (3/9) — both verified non-deterministic; full test:unit validated locally (16936 pass).
2026-06-22 03:17:02 -03:00
Diego Rodrigues de Sa e Souza
76a07cf7a5 Release v3.8.24 (#3747)
Release v3.8.24 — see CHANGELOG.md [3.8.24] for the full notes and the PR description for the contributors hall. Integration of release/v3.8.24 into main.
2026-06-13 17:27:40 -03:00
Raxxoor
5a7df8ac29 fix: harden stream readiness and build output (#2317)
Integrated into release/v3.8.0 — fixes stream readiness detection for OpenAI Responses API lifecycle events, GLM timeout, Provider Limits UI, and build output cleanup.
2026-05-16 21:47:40 -03:00
diegosouzapw
0cc6fec85e Merge PR #2280: feat(cli): CLI v4 — Commander.js, 50+ commands, TUI, i18n, plugins (Phases 0-9)
Complete rewrite of the OmniRoute CLI:
- Commander.js-based modular architecture (50+ command files)
- Full i18n support (en + pt-BR, 1222 keys each)
- TUI interactive interface (OAuthFlow, EvalWatch, ProvidersTestAll)
- Plugin system (omniroute-cmd-*)
- OpenAPI codegen (omniroute api <tag> <op>)
- Commands: serve, combo, compression, keys, tunnel, backup, test-provider,
  health, memory, MCP, A2A, oauth, skills, webhooks, usage, cost, eval,
  context-eng, dashboard, doctor, env, files, logs, models, nodes, oneproxy,
  open, openapi, plugin, policy, pricing, providers, quota, registry, repl,
  reset-encrypted-columns, resilience, restart, runtime, sessions, setup,
  simulate, status, stop, stream, sync, tags, telemetry, translator, tray, update
- Code review fixes: C1-C3, I1-I5, M1-M4 applied

# Conflicts:
#	bin/cli/commands/config.mjs
#	bin/omniroute.mjs
#	package-lock.json
#	package.json
2026-05-15 10:50:54 -03:00
diegosouzapw
b329cfc84a feat(cli): fase 8.13 — self-heal native deps (omniroute runtime check/repair/clean)
- bin/cli/runtime/nativeDeps.mjs: ensureRuntimeDir, hasModule, isBetterSqliteBinaryValid
  (ELF/Mach-O/PE magic bytes), npmInstallRuntime (shell:false, cmd.exe /c no Windows),
  ensureBetterSqliteRuntime, buildEnvWithRuntime com NODE_PATH extendido
- bin/cli/commands/runtime.mjs: subcomandos check/repair --force/clean --yes
- Registrado em commands/registry.mjs
- Chaves i18n runtime.* em en.json e pt-BR.json
- 9 testes unitários cobrindo todas as funções exportadas
2026-05-15 03:46:26 -03:00
diegosouzapw
54be51a664 chore(docs): remove all competitor references from branch
Remove every reference to the competing open-source project from docs,
comparisons, i18n mirrors, comments, and source files so the branch
history does not expose competitive intelligence.
2026-05-15 02:14:58 -03:00
diegosouzapw
a5fa94bdcb feat(cli): crash recovery com backoff exponencial e PID granular (Fase 1.9)
Adiciona ServerSupervisor (bin/cli/runtime/processSupervisor.mjs) que reinicia o
servidor com backoff exponencial (1s, 2s, 4s... cap 10s) em caso de crash.
Após maxRestarts falhas em 30s exibe crash log e encerra. Detecta MITM como
causa do crash via heurística e desabilita automaticamente.

PID management agora é granular por subprocesso (~/.omniroute/{service}/.pid)
suportando server, mitm e tunnel/cloudflared|tailscale. `stop` e
`killAllSubprocesses` encerram todos os serviços registrados.

Novas opções em `serve`: --log (passa stdout/stderr inline), --no-recovery
(comportamento legado sem supervisor), --max-restarts <n> (padrão 2).
2026-05-15 00:18:53 -03:00
diegosouzapw
8f915b18b0 feat(runtime): dynamic SQLite runtime installer with 5-step fallback chain
Adds bin/cli/runtime/sqliteRuntime.mjs that resolves better-sqlite3 from:
(1) bundled optionalDependency, (2) ~/.omniroute/runtime/ install,
(3) lazy npm install into runtime dir, (4) node:sqlite stdlib (Node >=22.5),
(5) bundled sql.js WASM. Each native binary is validated against expected
platform magic bytes (ELF/Mach-O/PE) before load.

Adds bin/cli/runtime/magicBytes.mjs with validateBinaryMagic() helper
(9 tests). Adds bin/cli/runtime/index.mjs as warmUpRuntimes() orchestrator.

Adds scripts/postinstall.mjs warm-up hook (non-fatal, skipped in CI).
Integrates it as the last step of scripts/build/postinstall.mjs.

Extends src/lib/db/core.ts with ensureDbInitialized() (async, idempotent)
and getDriverInfo() so the startup orchestrator can await the resolver
before any DB access, enabling graceful degradation without crashing the
process on missing better-sqlite3.

Solves Windows EBUSY error on 'npm install -g omniroute@latest' while the
previous version is still running, and works in environments without C++
build tools or with unreachable npm registry.

Documents OMNIROUTE_SKIP_POSTINSTALL in .env.example and ENVIRONMENT.md.

Ref: 9router/cli/hooks/sqliteRuntime.js (pattern origin).
2026-05-15 00:05:49 -03:00
diegosouzapw
77a4429bf4 feat(cli): add standalone system tray with PowerShell fallback on Windows
Cross-platform system tray for `omniroute --tray`: Windows uses a
PowerShell NotifyIcon script (zero native binary, AV-safe); macOS/Linux
use systray2 lazy-installed at runtime into ~/.omniroute/runtime/.
Includes per-platform autostart (LaunchAgent / .desktop / registry) and
`omniroute config tray <enable|disable>` CLI command.

DISPLAY added to env-sync allowlist as an OS/X11 variable, not an
OmniRoute config variable.
2026-05-14 22:42:01 -03:00