chore: update fork to upstream release/v3.8.51 + local deploy & playground fix - #1
Merged
Merged
Conversation
…n in-memory pending store (diegosouzapw#12854) Diagnóstico por captura de pacote em tráfego real, com o `400 previous_response_not_found` reassemblado do tcpdump três vezes no mesmo loop de tool-calling — isso é evidência, não hipótese. A causa é limpa: `detail_state` só vira `'ready'` depois de uma escrita fire-and-forget enfileirada num worker único, e o cliente já tem o id de resposta antes disso. Semear a ponte **antes do primeiro await** é o que faz a correção não custar latência. Revalidei sobre o tip: **19/19**, typecheck:core limpo, check-file-size OK. **Estava draft e eu marquei como ready.** Não havia gate declarado — nem RFC pendente, nem decisão de produto em aberto — e passou na validação; a diretiva permanente do dono para esta campanha é avaliar draft como qualquer PR e promover quando passa. Se a intenção era segurar por outro motivo, me avise que eu reverto. **Um conserto meu na sua branch.** O `typecheck:core` falhava com `TS2345` em `callLogs.ts:489` — e falhava **na sua branch sozinha**, não por interação com a onda; confirmei isolando. O call site fazia cast para `{ clientRawRequest?: unknown; clientResponse?: unknown }`, mais frouxo que o `ContinuationPipeline` que o parâmetro exige, e `unknown` não assina para os membros tipados. Exportei o `ContinuationPipeline` do próprio store e usei ele no cast, em vez de alargar o tipo do parâmetro: o contrato passa a ter um nome só, no lugar onde ele já vivia. **Sobre a sua Reviewer Note do Map sem limite de contagem:** concordo que vale registrar. Entradas pequenas com TTL de 60s auto-expirando não justificam sizing agora, mas se aparecer burst sustentado o sintoma será memória, não erro — e aí a nota está aqui. Também carreguei o rebaseline de `chatCore.ts` (6021→6026) e `stream.ts` (3080→3098), que a onda de streaming inteira faz crescer.
…nal (diegosouzapw#12639) (diegosouzapw#12988) * feat(api): hydrate memoryHits from the persisted history event `GET /api/a2a/tasks/[id]` falls back to the persisted history row once a task leaves the in-memory TTL window, and `reconstituteHistoricalTask` hard-coded `metadata: {}` — so the drawer's "Memory used" section vanished for any historical task, even though `executeA2ATaskWithState` had already written a `memory_hits` event with the hits. The fallback now reads that event: `data_json` is parsed and, when it yields at least one well-formed hit, exposed as `metadata.memoryHits`. The event itself is filtered out of `events` — it is observability, not a state transition, and without the filter it leaked into the timeline as a duplicate of the row's current state. Reading is defensive throughout, mirroring `DrawerMemory`'s own validation: the payload is caller-influenced and unvalidated end to end, so `JSON.parse` runs inside `safeJsonParse`, non-arrays are rejected, and each entry must carry `id`, `key`, `type` and `snippet` as strings (a non-string field would be rendered as a React child and take the drawer down). Malformed input degrades to `metadata: {}` and a 200 — never a 500. Refs diegosouzapw#12639 * fix(a2a): bound the memory recall with its own deadline collectMemoryHits() runs BEFORE the skill handler and had no deadline at all, so a slow memory backend delayed the start of every A2A task — the HTTP genericBackend alone defaults to a 30s timeout. The search now races a MEMORY_RECALL_TIMEOUT_MS (1500ms) deadline. Overshooting degrades exactly like any other recall failure: empty hits, a warn log, and the task proceeds normally (best-effort contract unchanged, nothing propagates). The deadline timer is cleared in a finally on BOTH paths so no handle is left holding the event loop open, and MemoryHitsDeps.timeoutMs makes it injectable so the tests cost milliseconds instead of 1.5s of wall clock. Refs diegosouzapw#12639 * fix(dashboard): carry conductor requirements and focus the repeated task The drawer's "Repeat" for a Conductor task dropped the runner/model pinning and left the operator staring at the finished run: - `hubTaskSchema` now parses the hub's `requirements` (`.catch(null)` so an odd shape never fails the whole task parse), and `ConductorTaskDetail` exposes `cli`/`model` (`null` when the hub sends none). - `repeatReqForConductor` carries `cli`/`model` when present and OMITS them otherwise — the route's Zod takes both as optional strings, so a `null` would 400. The two fields are independent. - `performAction` reads the response body once and returns it, so the repeat can report the CANVAS id of the created task (`task_id` / `data.id` / `result.task.id`, each with its node prefix). `OrchestrationPageClient` then refetches and focuses it via `?node=`; History keeps its current behavior. - `conductor-routes-auth.test.ts` covers the creation route through its `ROUTES` array; the duplicated source assertion left `conductor-create-route.test.ts`. Refs diegosouzapw#12639 * chore(a2a): follow-ups changelog Changelog fragment for the five items PR-C delivers from diegosouzapw#12639. The sixth item on the issue — an authenticated panel path for A2A task creation — stays deliberately out of scope and is recorded as such in a comment on the issue rather than silently dropped: the JSON-RPC endpoint accepts API keys only, and widening that endpoint's auth surface to serve a UI convenience is the operator's call, not the implementation's. Closes diegosouzapw#12639
… across flow surfaces (diegosouzapw#12378) (diegosouzapw#13203) * refactor(ui): move shared flow colors to the orchestration status tokens FLOW_EDGE_COLORS and TokenHealthBadge were pinned to the fixed dark-mode hexes in STATUS_HEX, so both rendered dark-theme green/amber/red on a light background. They now read the theme-aware --orch-status-{success,warning, error,muted} custom properties introduced in Fase 2. The dark values of those tokens are exactly the old hexes, so dark mode is unchanged and only light mode gains contrast. `idle` was already a CSS var, which is the precedent proving a var() resolves in a ReactFlow edge stroke. Five call-sites built translucent variants by concatenating an 8-bit alpha suffix onto the palette hex (`${FLOW_EDGE_COLORS.error}40`), which cannot work with a var(). They move to a new documented helper, flowColorAlpha(), that wraps color-mix() — the same approach orchStateBadgeBg() already uses in the orchestration model. Percentages mirror the old suffixes (20 -> 13%, 30 -> 19%, 40 -> 25%). STATUS_HEX stays exported as the dark-mode mirror; it now has no production consumer. globals.css needed no change — all five tokens already existed in both themes. The colour assertions in the topology, combo-live and design-grid suites were aligned to the tokens, never weakened: every hex equality became an equality against the corresponding var(). design-grid additionally now asserts each token is defined in BOTH themes. Refs diegosouzapw#12378 * refactor(ui): finish the status-token migration across flow surfaces Sweeps the five state hexes across the remaining flow surfaces, following D1: - ComboLiveStudio: active/error provider pills and the run-outcome tri-state. - CompressionCockpit / WaterfallInspector / IoNode: the savings readouts and the savings quality ramp (>=30 success, >=15 warning, else muted). - EngineNode: the same ramp, plus the running state, whose glow moved to flowColorAlpha — the literal #f59e0b40 suffix is invalid once the value is a var(). - WaterfallInspector: a skipped step now reads as muted rather than a bare grey hex. Deliberately NOT migrated, because they are categorical or brand palettes rather than state: STRATEGY_COLORS (routing-strategy hues), LAYER_COLORS (compression layer pills), the provider brand color in ProviderTopology, and IoNode's indigo/green input-output identity pair. The new test asserts both halves — what became a token AND what stays hex — so a later sweep cannot silently swallow a categorical palette. Refs diegosouzapw#12378
…egosouzapw#12876) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. Um health que diz "falhou" sem dizer **qual** conexão obriga o operador a cruzar logs para achar o óbvio. Expor os ids das que falharam é o que transforma o endpoint em ferramenta de diagnóstico.
…iegosouzapw#12882) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. Paginar e completar o stream em `GET /v1/models` é a correção certa para catálogo grande: um payload único que cresce com o número de providers vira timeout silencioso no cliente, não erro.
…n on one model 402 (diegosouzapw#12875) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. Envenenar a conexão inteira por um 402 de **um** modelo é o erro clássico de granularidade em provider openai-compatible com múltiplos upstreams — derruba modelos que estavam saudáveis. Restringir ao modelo afetado é o comportamento correto, e é a mesma distinção que o guia de resiliência faz entre cooldown de conexão e lockout de modelo.
…instead of "(empty)" (diegosouzapw#12727) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. "(empty)" para um nó de ferramenta ainda não resolvido é informação errada, não ausência de informação — o usuário lê como "não retornou nada". Spinner de pendente diz a verdade.
…egosouzapw#12937) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. Sentinela que não se explica (`—`, `?`) faz o leitor inventar a razão. Explicar no hover é metade; o `check:radar-sentinels` é a outra — sem o gate, a explicação apodrece na primeira coluna nova.
…iegosouzapw#12884) Um `ALL_TARGETS_SKIPPED` 503 que não diz qual janela esgotou é opaco justamente no momento em que o operador mais precisa saber. Alinhar os rótulos de janela AUTH com os da API de uso fecha a outra metade: dois nomes para a mesma coisa fazem o dashboard e o erro parecerem discordar. Revalidei sobre o tip: **6/6**, typecheck:core limpo, check-file-size OK. **Dois consertos meus na sua branch.** 1. `typecheck:core` falhava com `TS2345` em `comboAttemptLoop.ts` (linhas 130 e 416): o `QuotaSkipTarget` declarava `connectionId?: string`, mas o `ResolvedComboTarget` carrega `string | null` para alvo não-pinado. Alarguei para `string | null` no tipo de diagnóstico em vez de estreitar o call site — o módulo só **lê** o campo e a linha 29 já narrowa com `typeof === "string"`, então null não custa nada ali. Isso apareceu porque o `comboAttemptLoop` mudou de forma no diegosouzapw#12746/diegosouzapw#12811, mergeados nesta mesma campanha depois que você cortou a branch. 2. O `roundRobinCombo.ts` foi de 1198 para 1205 e cruzou o teto de 1200 para arquivo novo. Congelei com justificativa: o arquivo já nasceu em 1198 quando o diegosouzapw#12811 o levantou de dentro do `combo.ts`, e os diagnósticos em si vivem no `quotaSkipDiagnostics.ts`, sob o cap. Registrei que a próxima extração natural é o corpo do attempt loop, mas que ele acabou de ser movido e deve assentar antes de ser cortado de novo.
…iegosouzapw#12715) Fila sem teto que segura a request seis minutos até o cliente abortar é pior que 503 imediato: consome slot, mascara a saturação e ainda entrega erro no fim. Um orçamento `maxWaitMs` por conexão compartilhado entre gate, slot padrão do provider e fila do Bottleneck é a forma certa — o teto tem que ser um só, senão cada camada espera o seu. O `max(perConn, upstream)` no `executionMaxWaitMs` é o detalhe que evita a correção matar request em voo, que seria trocar um defeito por outro. Registro a atribuição: você manteve o diegosouzapw#12635 aberto para o @Tushar49 e creditou a percepção dele (providers lentos precisam de 2min→10min por conexão) enquanto adiciona o encanamento que faltava. É o jeito certo de construir sobre PR de outra pessoa sem tomar o crédito. Sobre o `npm run lint` desmarcado com a nota do eslint quebrado no ambiente: deixar em branco e explicar vale mais que marcar sem ter rodado. Rodei aqui: limpo. Revalidei sobre o tip: **13/13**, typecheck:core limpo, check-file-size OK. O `file-size-baseline.json` conflitou com os rebaselines desta campanha — resolvido aditivamente, JSON revalidado com `json.load`.
…diegosouzapw#12717) Uma conversa que nunca chegou a parada limpa e não sinaliza nada é o pior estado possível de UI: indistinguível de uma que terminou. O incidente que você cita no comentário do teste — stream pesado em reasoning estourando o cap do coletor no meio, deixando a conversa presa sem sinal — é exatamente o caso que justifica o badge. Separar `resolveTurnCompletionState` de `resolveConversationStalledState` também está certo: `tool_call_pending` é um estado legítimo em voo, não uma conversa travada. Revalidei sobre o tip: **29/29**, typecheck:core limpo. **Nota de integração.** O `tests/unit/responses-continuation-store.test.ts` conflitou com o diegosouzapw#12854, que anexa a própria bateria ao mesmo arquivo. Reconstruí o arquivo como append limpo — versão do tip mais o seu bloco de 184 linhas, verificado por `esbuild` antes de rodar. Registro por que importa: na primeira tentativa eu apenas retirei os marcadores de conflito, e isso enfiou os seus testes **dentro** de um objeto literal não terminado do diegosouzapw#12854. Compilava como erro de transform, não como conflito — só apareceu ao rodar. Resolver JSON e teste "aditivamente" sem verificar a sintaxe depois é armadilha; ficou a lição.
…eights, quality gate and scoring diagram now covered (diegosouzapw#12507) Estender o gate de contagens para headings, rankings, catálogo, pesos, quality gate e o diagrama de scoring é exatamente o tipo de trabalho que evita a classe inteira em vez de um caso. Falo por experiência desta campanha: o `check:docs-counts` caiu **duas vezes** hoje pela mesma causa — contagem de migration escrita à mão em três arquivos mais 41 mirrors, desatualizando a cada migration nova (diegosouzapw#12970 e diegosouzapw#13209). Cada superfície que este PR passa a cobrir é uma que deixa de virar base-red na mão de quem vier depois. Revalidei sobre o tip: **19/19**, `check:docs-counts-sync` com 0 drifts, `check:docs-all` PASS, `check:doc-links` PASS. **Integração:** dois conflitos. 1. `scripts/check/check-docs-counts-sync.mjs` — o bloco de leitura de fatos conflitou com os imports de free-tier que entraram pelo diegosouzapw#12786/diegosouzapw#12744 nesta campanha. Aditivo, os dois conjuntos ficaram. 2. `docs/diagrams/auto-combo-scoring.mmd` — o seu rótulo dizia `reliability (0.0000)`, mas o diegosouzapw#12731 mergeou horas antes e passou a dar peso de reliability a todo mode pack. Ficou o rótulo do tip, `reliability (0.0000 DEFAULT, 0.03 packs, 0.04 reliable)`, que é o número real agora.
…les (diegosouzapw#13229) `check:mutation-test-coverage --strict` has been failing Fast Quality Gates on every open PR against release/v3.8.51. It grew from 2 missing entries to 5 in roughly an hour, so it is drifting faster than PRs land. Four test files cover a mutated module without being listed, so their mutant kills do not count: open-sse/services/accountFallback.ts <- openai-compatible-per-upstream-402-health src/sse/services/auth.ts <- openai-compatible-per-upstream-402-health <- quota-window-label src/shared/utils/circuitBreaker.ts <- combo/execute-target-gates open-sse/services/combo/comboStructure.ts <- combo-pin-implicit-allowlist Registration only — no test or module is touched, and no gate is weakened; the listing is what makes those kills count in the first place. Inserted in place, never through a JSON round-trip: re-serializing this file reorders the ~10 curated entries that are already out of alphabetical order (learned the hard way in diegosouzapw#11438). check:mutation-test-coverage now reports no drift. check:tracked-artifacts OK, prettier clean. Worth noting for whoever adds the next test: this gate fires whenever a NEW test happens to cover one of the 31 mutated modules, which is easy to do without realising. Registering it in the same commit is cheaper than a CI round-trip.
diegosouzapw#13228) CodeQL js/useless-regexp-character-escape (diegosouzapw#994-diegosouzapw#997) on one line, and it is a real defect rather than the usual query noise. The assertion built its pattern in a TEMPLATE literal: new RegExp(`\(\s*${String(tos?.actual)}\s*\)`) JavaScript resolves the escapes before RegExp ever sees the string: `\(` becomes "(" and `\s` becomes the LETTER "s". The compiled pattern was `(s*16s*)` — a capture group around optional "s" characters — so it matched any heading merely CONTAINING the number. The literal parentheses this guard exists to require were never checked, and it passed on exactly the headings it was written to reject: /(s*16s*)/.test("### Caution — clauses worth checking 16") // true Doubled the backslashes so they survive the template literal, and routed the interpolated value through an `escapeRegExp` helper — the count is a number today, but interpolating an unescaped value into a regex source is the same class of bug one refactor away. Added a second test that pins the behaviour rather than the spelling: the pattern must REJECT a heading carrying the count without parentheses, and accept it with them (including inner whitespace). Before this fix that test fails. 4/4 green against the real docs/reference/FREE_TIERS.md heading.
…w#13211) GHSA-wvxc-jp3v-5mg5: `DELETE /api/v1/batches/delete-completed` deleted the completed batches of EVERY api key on the instance and nulled the contents of every file those batches referenced. Any ordinary inference key reached it — including one with `scopes: []` — and no victim key, batch id or file id was needed. Two defects stacked in one endpoint: - `deleteCompletedBatches()` carried no `api_key_id` predicate. The file SELECT, the checkpoint DELETE and the batch DELETE were all instance-wide. - The route only checked that SOME key was present (`!scope.apiKeyId` → 401), never that the caller owned anything, and called the helper bare. The helper now takes `apiKeyId` and scopes all three statements to it; the route passes the caller's key and omits it only for session auth, so the operator's own dashboard keeps its instance-wide cleanup and an API key clears only its own completed batches. None of this is a new pattern. `listBatches(apiKeyId?)` and `countBatches(apiKeyId?)` in the same module already scope by `api_key_id`, and `batches/[id]/route.ts` already gates per-record access with `scopeCheck` — session auth sees everything, a key sees only its own. This one helper was the one that never got it, which is why the fix reuses the shape instead of inventing a second convention. tests/unit/batch-delete-completed-ownership-wvxc.test.ts — 5 tests, 4 red before the fix, including the two that prove the cross-tenant destruction (another key's batch survives; another key's file content survives). It also pins the instance-wide dashboard sweep so the fix cannot be "tightened" into breaking the operator's own cleanup, and a source guard that the route never calls the helper bare again. Reported privately via GHSA-wvxc-jp3v-5mg5. Closes GHSA-wvxc-jp3v-5mg5
…pw#13213) * chore(deps): drain the Dependabot queue — 7 of 10 alerts Lockfile-only bumps; no manifest touched, so nothing changes for consumers. Root package-lock.json: hono 4.13.0 -> 4.13.7 (diegosouzapw#215 diegosouzapw#216 diegosouzapw#217, medium, patched 4.13.5) csv-parse 7.0.1 -> 7.0.2 (diegosouzapw#213, medium) joi 18.2.3 -> 18.2.8 (diegosouzapw#211 diegosouzapw#212, low, patched 18.2.4/18.2.5) @omniroute/opencode-plugin: toml 4.1.1 -> 4.3.0 (diegosouzapw#209, HIGH, patched 4.1.2) @omniroute/opencode-plugin-v2: esbuild 0.28.1 -> 0.28.2 (diegosouzapw#210, low) — the direct copy only; see below. The plugin-v2 diff looks large but is one package: esbuild ships 27 platform binaries, each carrying version + resolved + integrity. Three alerts stay open, deliberately: diegosouzapw#218 extract-zip (HIGH) and diegosouzapw#214 adm-zip (medium) have NO published patch. Both are dev-scope. Closing them needs an upstream release or a decision to replace the dependency — neither belongs in a lockfile bump. diegosouzapw#210 esbuild is only half-closed. `node_modules/esbuild` is on 0.28.2, but `tsup` pins `esbuild: ^0.27.0`, so its nested copy stays at 0.27.7 — inside the vulnerable range (>= 0.27.3, < 0.28.1). Updating tsup does not move it (8.5.1 is already current). Forcing it would take an `overrides` entry pushing a major of esbuild inside the bundler, which is exactly the change that breaks a build silently, for a LOW dev-only alert. Left for an upstream tsup release. check:lockfile passes on all three, including the workspace lock/manifest consistency check. check:tracked-artifacts OK. * chore(deps): bump js-yaml to 4.3.2 (root + electron) Two more HIGH alerts arrived after the first sweep: diegosouzapw#220 js-yaml (root package-lock.json) >= 4.0.0, < 4.3.2 diegosouzapw#219 js-yaml (electron/package-lock.json) >= 4.0.0, < 4.3.2 The root's own js-yaml was already on 5.4.1; the vulnerable copies were the ones nested under @yarnpkg/parsers, lockfile-lint, xmlbuilder2 (root) and the direct dependency in electron. All now 4.3.2. Four version lines, nothing else. diegosouzapw#221 smol-toml (HIGH, <= 1.7.0) is NOT closed here. The root is on 1.8.0; the vulnerable 1.6.1 sits under @openai/codex-security, which pins it as an EXACT version rather than a range, so `npm update` cannot move it. Bumping codex-security itself (0.1.24 -> 0.1.26) does not help — 0.1.26 pins the same 1.6.1 — so that bump was reverted rather than carried along for no benefit. Closing diegosouzapw#221 needs an upstream codex-security release or an `overrides` entry, the same trade already declined for diegosouzapw#210/tsup: forcing a transitive pin from outside is how a build breaks silently. Note that @openai/codex-security is also the package carrying the unpatched extract-zip (diegosouzapw#218), so one upstream release would likely clear both. * chore(deps): override smol-toml to 1.8.0 and raise the js-yaml floor Closes diegosouzapw#221 (smol-toml, HIGH, DoS via malformed TOML, vulnerable <= 1.7.0). @openai/codex-security pins smol-toml at 1.6.1 as an EXACT version, so no `npm update` reaches it. This repo already uses `overrides` as its standard tool for exactly that situation — the block carries 20+ entries, including the scoped-by-parent form and the `qs`/`fast-uri`/`ip-address` entries that back earlier security bumps — so a scoped override is the idiomatic fix here, not a new mechanism: "@openai/codex-security": { "smol-toml": "^1.8.0" } The nested copy deduplicates to the root's existing 1.8.0, which two other consumers (the root itself and knip) already run, so the version is proven in this tree. The whole lockfile diff is the 14 lines of the removed 1.6.1 entry. Also raised the `@yarnpkg/parsers` js-yaml floor from ^4.3.1 to ^4.3.2, so the override documents the patched version rather than permitting the vulnerable one it was written against. Not fixed, and not fixable by version — verified against the npm registry rather than trusting the advisory metadata: diegosouzapw#218 extract-zip — latest published IS 2.0.1, the vulnerable version. Dev scope, via @openai/codex-security. No release to move to. diegosouzapw#214 adm-zip — latest published IS 0.6.0, the top of the vulnerable range (>= 0.5.9, <= 0.6.0). RUNTIME scope, via onnxruntime-node's ^0.5.16, and the repo already overrides adm-zip to ^0.6.0. No release to move to. Both need an upstream fix or a decision to replace the dependency; neither is a lockfile change. adm-zip being runtime rather than dev makes it the one worth tracking. diegosouzapw#210 esbuild stays open too. A flat `overrides: { esbuild: ^0.28.2 }` in opencode-plugin-v2 does close it — npm then reports 0 vulnerabilities — but it requires regenerating that lockfile from scratch: 823 lines, 96 packages moved, for a LOW dev-only alert, and a major esbuild bump inside tsup cannot be validated here without a real install of that package. Tried, measured, reverted. Left for an upstream tsup release. check:lockfile OK on all lockfiles including the workspace consistency check; check:tracked-artifacts OK; prettier clean.
… it (diegosouzapw#12918) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…a turn (diegosouzapw#12920) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…t anthropic (diegosouzapw#12921) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…pplies (diegosouzapw#12896) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…_tokens (diegosouzapw#13007) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…13066) (diegosouzapw#13083) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…lots (diegosouzapw#13110) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…etions (diegosouzapw#13070) (diegosouzapw#13087) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…osouzapw#12930) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
diegosouzapw#13104) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
…apw#13009) Approved by the maintainer for the agent-instruction surface it touches: the SKILL.md change is regenerated output from the corrected parser (`resilience set` -> `resilience set <name>`), restoring the required argument the published page had been hiding. No hand-written directive was added. Boarded with 13 sibling PRs and validated as a set: 132 focused tests pass, typecheck:core clean, changelog integrity and file-size gates green. Thank you — the table contrasting the declared argument against the published page is what made the second case (an agent told to run `resilience set` with no argument) visible as more than cosmetic.
…ouzapw#13024) Merged with a rebaseline commit added on top of your branch: check:file-size freezes the gateways catalog at 1462 lines, so any new entry fails the gate on arrival. The annotation covers this entry and EURouter's (diegosouzapw#13025) together, following the route every previous gateway entry took (diegosouzapw#11786 seekai, diegosouzapw#10987 logfare, diegosouzapw#10668 tabitoken, diegosouzapw#10531 freebuff, diegosouzapw#11631 1min.ai) — the file is declarative data already split into six family files, so splitting it for two entries would break the semantic-families rule. Validated in a combined worktree with 13 sibling PRs: 132 focused tests pass, typecheck:core clean, file-size green after the rebaseline. Thank you for stating plainly what you did not verify. "The endpoint exists and is key-gated; catalog, streaming and tool calls not exercised" is worth more than a confident entry that turns out to be guesswork, and the conservative entry that follows from it — empty models, no capability declared, hasFree false with the billing shape spelled out — is exactly right.
…ouzapw#12985) (diegosouzapw#13025) Rebased onto the release tip after diegosouzapw#13024 landed: both PRs extend the same three registration files, so the sibling merge turned this into a conflict. The resolution is additive — both catalog entries kept, both registry imports kept, both base URLs kept — and EURouter stays in AGGREGATOR_PROVIDER_IDS while GreenPT stays out, exactly as each PR argued. 14 provider tests pass on the rebased branch and the file-size gate is green under the annotated rebaseline. Thank you for re-checking the endpoint live instead of trusting the report, and for the sovereignty caveat. Naming the upstreams from EURouter's own catalog — Claude Sonnet served by AWS Bedrock, 19 models owned by openai — and then writing an apiHint that says routing rather than residency is the kind of care that keeps a provider entry honest. The test asserting the copy contains none of "residency", "stays in the EU", "EU-hosted" or "sovereign" is a good guard against that drifting later.
…in path (diegosouzapw#13672) Behind the new `RETRY_AFTER_PROVENANCE_ENABLED` flag (default off): `unavailableResponse` omits the synthetic `Retry-After: 1` when there is no real retry signal, marks `retry_after_provenance` on its bodies, and both combo drain readers parse prose retry hints from plain-text bodies too. With the flag off, headers and bodies are exactly as before. Maintainer rework before merge (kept the idea, no default behavior change): - A past `Retry-After` date is no longer labelled as an upstream signal with `Retry-After: 1`; non-JSON bodies (HTML 502 pages) log at debug instead of warning on every request. - The provenance claim is narrowed to responses built by `unavailableResponse`, documented in the flag row. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…ouzapw#13439) Behind the new `PROTECTED_PRIORITY_INFRA_502_ENABLED` flag (default off), protected-priority combo stops caused by provably non-quota infrastructure (provider circuit open, predictive-TTFT latency) surface as 502 instead of a quota-looking 503. Maintainer rework before merge (kept the idea, no default behavior change): - The original branch made 502 the default for every stop, including model lockouts and cooldowns, and removed the diegosouzapw#8133/diegosouzapw#1731 provider-wide skip for 401/5xx without a connection id; both are restored with their regression tests untouched. - Nineteen cases cover eight gate causes plus predictive latency, flag off and on. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…rs plus fire-and-forget async (diegosouzapw#13614) Stale provider-pin (`clearStaleLKGP`) clears are no longer silent: the fire-and-forget promise carries a `.catch` that warns with combo, comboId and executionKey, and a `check:routing-error-guard` npm script keeps the inventory of swallowed catches in the routing hot path from growing. Maintainer rework before merge (kept the idea, no default behavior change): - The awaited DB writes in the fallback loop were reverted (they added latency and SQLite lock exposure on every skip); the clear stays non-blocking. - The guard keys its allowlist by file + normalized catch body instead of line numbers (the PR's version broke on any edit) and is wired as an npm script only, not in CI; the unused stats counters were dropped. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…d off-by-default flag (diegosouzapw#13633) Behind `STREAM_RECOVERY_TOOLCALL_ORDER_FIX` (default off), mid-stream continuation becomes tool-call safe: any tool call seen in the stream — in flight or finished — blocks a continuation, and an empty continuation stops after one attempt. Maintainer rework before merge (kept the idea, no default behavior change): - The empty-continuation short-circuit also ran with the flag off; it is now gated, so the flag-off path uses the whole budget exactly as before (regression test added). - The latch re-arm that let a continuation fire after a completed `finish_reason: tool_calls` is gone; index-less tool calls on multi-choice payloads are now blocked too; ~150 lines of dead trace plumbing removed. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…iegosouzapw#13650) Recovery traces for mid-stream continuation: one `onContinueOutcome` hook reports suffix stitched, overlap rejected, terminal, empty, no-stream and refused (with reason), logged through `chatCore` at debug level; warn is reserved for the cases where recovery gives up. Maintainer rework before merge (kept the idea, no default behavior change): - The original logged a warn-level latch line on every streamed tool call; nominal and tool-call streams are now silent, and the existing `mid-stream continuation attempt N/4` line keeps its format. - `chatCore.ts` ends 3 lines shorter than the tip, so the baseline bump the PR carried was removed; the wiring is tested through a real `handleChatCore` continuation. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…only on partial estimates (diegosouzapw#13686) Estimated token usage is now visible to operators: usage a provider marks as `estimated` carries an internal marker through extraction and the call log records `_omniroute.usageEstimated: true` on the logged response. Maintainer rework before merge (kept the idea, no default behavior change): - Billing is unchanged: the original skipped cost/budget/quota-share for estimated usage, which would have let streams without upstream usage and eight web executors spend $0 against API-key budgets; that part is reverted and no opt-in flag was added because it cannot be made budget-safe. - Both open-sse TS2345 errors, the client-visible `estimated_prompt_tokens` field and the `as unknown as` casts are gone; four real `handleChatCore` cases assert the marker and unchanged spend. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…ouzapw#13153) Behind `STREAM_EARLY_EOF_SIBLING_FAILOVER_ENABLED` (default off): after the bounded same-connection retry is spent, a stream that closed early fails over exactly once to a sibling connection. Maintainer rework before merge (kept the idea, no default behavior change): - The PR's own failover test was red on its head: the `/v1/chat/completions` route's early-stream keepalive dropped the `X-OmniRoute-Selected-Connection-Id` header on the first cold request. Tests now drive `handleChat()` directly; the assertion was kept. - "One hop" was one hop per connection (a 3-connection pool made 4 dispatches); it is now a single sibling hop per request, and when the pool runs out the original `STREAM_EARLY_EOF` 502 is returned instead of a generic `bad_gateway`, so combo-level detection keeps working. The source-regex timeout test became a behavioral one; flag description in all 59 locales. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
… obsidian always-protected, DATA_DIR vault refusal (diegosouzapw#13791) GHSA-7pq4-8pvv-rx7r (critical). Every link of the reported chain held on the release tip: 1. First boot without JWT_SECRET generates one and writes it in cleartext to $DATA_DIR/server.env. 2. With no password configured, isAuthRequired() returned false for POST /api/settings/require-login unconditionally — before the loopback check — so any network peer could switch requireLogin off. 3. With requireLogin off, POST /api/settings/obsidian/webdav accepted an arbitrary vault root and echoed freshly minted Basic credentials. 4. The WebDAV file service is served by the custom Node layer before Next.js, outside the authz pipeline. 5. Pointing it at DATA_DIR reads server.env, and JWT_SECRET forges an `{"authenticated":true}` admin session. A second, worse problem surfaced while verifying: isLoopbackRequest() decided "loopback" from nextUrl.hostname / the Host header, which the client controls. `Host: localhost` from a remote address made the whole fresh-install bootstrap reachable, not just the write path. Three cuts, plus the root cause: - isLoopbackRequest() now reads the trusted peer: the token-stamped real TCP peer the custom server writes (peerStamp), then the pipeline's own locality verdict once a stamp token exists, then a real socket peer. The bootstrap write path honours the same constraint instead of returning false, and managementPolicy hands down the peerContext verdict explicitly, because at policy time the original request still carries client-supplied headers. - Host is consulted only when the process has no stamp token at all — no stamping server in front, which in practice means route handlers invoked directly by the unit-test harness. Every supported runtime (run-next dev and start, standalone-server-ws for Docker, the npm CLI and Electron) calls ensurePeerStampToken() at boot, so there a signal-less request fails closed. Without this fallback ~340 route tests that call handlers with `new Request("http://localhost/…")` turned into 401s. - /api/settings/obsidian joins ALWAYS_PROTECTED_API_PATHS: issuing and rotating reusable WebDAV credentials is credential export, the same rationale as the GHSA-62vw entry for the password reveal. - enableObsidianVaultSync() refuses a vault that is, sits inside, or contains DATA_DIR, comparing realpath-resolved paths so a symlink cannot dodge it. Tests are red-first: remote stamped peer → auth required on the bootstrap write; Host: localhost plus a forged locality header from a non-loopback stamped peer → 401 through the full pipeline; the local operator keeps the first-password flow; obsidian inventory and DATA_DIR overlap cases.
…#13578) Behind the new `PROXY_SKIP_RECENTLY_FAILED` flag (default off), pool rotation and the opencode account rotation remember a proxy that just failed (refused probe or 429) and skip it for a doubling cooldown instead of re-serving it immediately. Maintainer rework before merge (kept the idea, no default behavior change): - The original was on by default and re-queried the DB on every request while a member was set aside; selection now caches a refusal sequence number and re-runs the cascade once per set-aside event. - `src/lib/db` no longer imports the heavy dispatcher for key normalization (a parity test guarantees the same key as `proxyConfigToUrl()`); `.env.example` and `ENVIRONMENT.md` document the default as false. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…diegosouzapw#13580) `proxy_logs` records `upstream_status`, the HTTP status the provider actually returned through the proxy, instead of only success/timeout/error. Maintainer rework before merge (kept the idea, no default behavior change): - The migration collided with the tip (177 was already taken): renumbered to `179_proxy_logs_upstream_status.sql`, the runner's already-applied check moved to `case "179"` (the old `"177"` would have skipped the tip's own 177), migration count bumped to 176 in README, AGENTS.md, llm.txt and its mirrors (operator-approved). - A new test runs the real migration runner on the real SQL files and fails with the old case number. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
diegosouzapw#13602) Behind `PROXY_SKIP_RECENTLY_FAILED` (from diegosouzapw#13578): a provider 429 received through a pool member sets that member aside and a 2xx clears it, for opencode providers. Maintainer rework before merge (kept the idea, no default behavior change): - `noteProxyOutcome` ran inside the fire-and-forget `safeLogEvents` after awaited dynamic imports, so a concurrent request could still pick the member; it now runs first, synchronously, before any `await`. - The duplicate `177_proxy_logs_upstream_status.sql` the stack still carried alongside the renamed 179 was removed; the regression test the PR body named exists as `pool-ip-quota-429-path.test.ts`. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…iegosouzapw#13581) Behind the new `PROXY_POOL_EGRESS_OBSERVATION` flag (default off): a line under each proxy pool showing how many distinct egress IPs actually served it over 24h, backed by `GET /api/settings/proxies/pool/egress-observation`. Maintainer rework before merge (kept the idea, no default behavior change): - The route validates its query with Zod (unknown `scope` → 400 instead of silently `global`), error bodies go through `errorResponse()`, the OpenAPI entry documents security, parameters and responses, and the three UI strings exist in every locale. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…ure streak (diegosouzapw#13608) Behind the new `PROXY_HEALTH_BLOCKED_RESETS_STREAK` flag (default off), a probe the target refuses (401/403/429) resets the proxy's consecutive-failure streak, so a proxy that clearly relays is not marked dead by spaced-out real failures. Maintainer rework before merge (kept the idea, no default behavior change): - The original reversed the deliberate diegosouzapw#10654 policy for everyone; with the flag off a refusal stays neutral, and the existing assertions are restored. The stale JSDoc and the wrong "any relayed response resets" comment are fixed (5xx stays inconclusive). - The source-grep test became a real sweep test: a local relay answering 403 drives fail → blocked → fail with auto-disable, in both flag modes. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…first byte (diegosouzapw#13484) Behind the new `OPENCODE_RESPONSES_STALL_ROTATION` flag (default off): a streamed Responses reply with no first body byte within `RESPONSES_FIRST_BYTE_TIMEOUT_MS` (15s) cools the account and rotates once; a second stall fails fast instead of waiting the 80s readiness timeout. Maintainer rework before merge (kept the idea, no default behavior change): - The TLS first-byte watchdog from diegosouzapw#12656 is restored byte for byte (the PR had changed its pump, timer and cancel); the stall guard lives in its own module. - Proxy-less multi-account setups now rotate the same way as proxied ones (the original threw for them), a client abort during the wait rethrows instead of rotating, and the env var is documented as flag-only. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…pw#13498) Behind the new `OPENCODE_USER_BLOCKED_ROTATION` flag (default off), a 403 or 451 carrying `user_blocked` on a proxied opencode account rotates at most once to the next account instead of being returned as-is. Maintainer rework before merge (kept the idea, no default behavior change): - 403 and 451 are handled by one predicate (the original returned 451 without rotation), the refused account gets a cooldown and joins the tried-set, and the response body of the attempt rotated away from is cancelled. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…ailures (diegosouzapw#13615) Behind the new `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` flag (default off), after two consecutive transient upstream failures the opencode rotation pauses before each later account (1.5s, 3s, 6s, capped at 10s per request) instead of hammering the upstream. Maintainer rework before merge (kept the idea, no default behavior change): - The pause honors the client abort signal (no dispatch after a disconnect), the failed attempt's body is cancelled before sleeping, `transientRetryDelayMs` now uses its arguments, and the sleep is injectable so the tests run without real timers. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…o clear auth failure (diegosouzapw#13609) Behind the new `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` flag (default off), a bare Mistral 401 (`{"detail":"Unauthorized"}`, identical for a revoked key and an exhausted quota) gets a retryable cooldown instead of parking the connection as `expired`; after three soft strikes within an hour the next bare 401 parks it, so revocation still converges. Maintainer rework before merge (kept the idea, no default behavior change): - The predicate is shared with the connection-test module instead of duplicated; the squeezed 139-char line that dodged the file-size gate is formatted normally and the growth is rebaselined honestly with an annotation. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…d ERROR_TYPE_CONTRACT import left by the batch merges (diegosouzapw#13816) Merged with admin on local + CI evidence: `tests/unit/i18n-catalogs-no-duplicate-keys.test.ts` red on the tip (59 catalogs) → `pass 3 / fail 0` here; **API Route Typecheck passes on this PR** (it fails on every PR based on the current tip because of the duplicated `ERROR_TYPE_CONTRACT` import this removes); CodeQL, semgrep, Vitest fast-path, Docs gates, Change Classification pass. The remaining red checks (Fast Quality Gates, Merge integrity, Unit Tests fast-path 1/2/4) are the same inherited tip reds every PR on release/v3.8.51 shows right now — diegosouzapw#13747 sweeps them. Both removed lines were byte-identical duplicates; nothing parsed or typed changes.
…gosouzapw#13657) The opencode executor classifies rate-limited 429 bodies (`classify429`, with real tests) and, when a whole account wave is exhausted, returns the last real upstream 429 — status, body, `Retry-After` and quota headers intact — so the provider error rules (monthly-quota cooldown) keep working. Maintainer rework before merge (kept the idea, no default behavior change): - The original stopped the cross-account wave at the first classified 429 and replaced the response with a synthetic one that dropped the body and headers; stopping early is now opt-in behind `OPENCODE_RATE_LIMITED_429_EARLY_STOP` (default off), the rate-limited account is still cooled down, the body is read as a bounded 8 KiB prefix from a clone and the original is never consumed, and the unused `status` input is gone. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis!
…(stryker, CLI i18n, paid-target fixture, call-log traceId, Jina prefix, callLogStats import, gitleaks) (diegosouzapw#13747) * fix(ci): clear the release/v3.8.51 base-reds left by the 09-15 batch — stryker coverage, CLI ready_timeout key, paid-target fixture, call-log traceId, Jina custom prefix Every PR into release/v3.8.51 pushed after diegosouzapw#13635/diegosouzapw#13678 still failed Fast Quality Gates and all four Unit fast-path shards on the same 16 tests. Each one reproduces on the pure tip; none is a product defect: - mutation-test-coverage: noauth-model-lockout and local-token-budget-429-skips-cooldown (diegosouzapw#13606) were missing from stryker.conf.json tap.testFiles. - cli-i18n-catalog: --ready-timeout calls t("serve.ready_timeout") with no catalog entry; added to en, zh-CN and zh-TW (the parity-checked locales). - paid-model-target(-routes)-6540: diegosouzapw#13407 removed Together's one-time credit from the free catalog, so "together/..." classifies as unknown and the save-time guard correctly lets it through. Fixture is now gemini/gemini-3.1-pro-preview, plus a precondition test on the fixtures. - attempt-logging-early-keepalive-merge / video-bridge-log-redaction: diegosouzapw#13546 keys the call-log row on traceId; baseCtx now defaults traceId to pendingRequestId (same pattern as chatcore-attempt-logging). The keepalive test also moves to the 30s wall-clock poll deadline video-bridge uses. - models-catalog-route: custom Jina rows keep the jina-ai/ prefix; diegosouzapw#13403 changed the custom assertion to jina/ (only synced rows use the alias). Refs diegosouzapw#12732 * fix(ci): clear the four reds the first r4 CI run surfaced — callLogStats duplicate import, Uzbek gitleaks false positive, redaction probe traceId, file-size - src/lib/db/callLogStats.ts: the diegosouzapw#13641 merge left ERROR_TYPE_CONTRACT imported twice (TS2300), failing API Route Typecheck and check:dashboard-typecheck on every PR. - .gitleaks.toml: the Uzbek catalog from diegosouzapw#13727 translates outputTokenDesc as "Yakunlash/javob tokenlari"; generic-api-key reads it as a token value. - dashboard-request-failed-redaction-probe: reads the persisted row by traceId (diegosouzapw#13546); with pendingRequestId it asserts null. - models-catalog-route: drop the explanatory comment, which pushed the frozen file over its size cap; the rationale lives in the changelog fragment. Refs diegosouzapw#12732 * fix(ci): re-freeze the two test files diegosouzapw#13748/diegosouzapw#13749 grew past their file-size caps PR-mode check:file-size relaxes source files against the base but not testFrozen, so image-generation-handler.test.ts (2133->2235, diegosouzapw#13748) and batch_api.test.ts (1345->1348, diegosouzapw#13749) failed Fast Quality Gates on every PR, this one included. Caps set to the merged LOC, with the justification entry. Refs diegosouzapw#12732 * fix(ci): register free-badge-provider-gate (diegosouzapw#13645) in stryker tap.testFiles diegosouzapw#13645 landed a covering test for src/sse/services/auth.ts without the stryker entry, so the strict mutation-test-coverage gate went red again. Refs diegosouzapw#12732 * fix(ci): clear two more base-reds the diegosouzapw#13440/diegosouzapw#13439 merges added - stryker.conf.json: register daily-reset-tz-threading (diegosouzapw#13440), which covers accountFallback.ts and rrState.ts. - .gitleaks.toml: allowlist the PROTECTED_PRIORITY_INFRA_502_ENABLED flag id (diegosouzapw#13439); generic-api-key reads its key: as a token (secrets ratchet 0 -> 1). Refs diegosouzapw#12732 * docs(changelog): tidy the stryker base-red fragment wording Refs diegosouzapw#12732
…; ratio gate now blocking (diegosouzapw#13782) PR-4 of the locale-expansion plan. 215,363 strings retranslated across the 65 catalogs with the new `sync-ui-keys --retranslate-identical`; the share of leaves still identical to English drops from a mean of 18.3 % to 1.8 % (Spanish 56 → 2.1). No `__MISSING__` marker or missing key is left; zh glossary normalised; pinned product/flag names kept English and allowlisted. Baseline tightened and the CI step `i18n real-translation ratio` is blocking from here on.⚠️ base-red inherited: diegosouzapw#12732
…#13795) On a 429 the opencode executor now records the refused proxy's key in the request-local tried-set, exactly like the 403/451, 5xx, stall and network arms already did — so a second account sharing that same proxy is not dialed and refused again before the loop reaches a genuinely different route (direct, or another proxy). Reviewed against the tip that already carries your 38 merges from this evening: this is additive to the flags that landed today (`OPENCODE_RATE_LIMITED_429_EARLY_STOP`, `PROXY_SKIP_RECENTLY_FAILED`, `OPENCODE_USER_BLOCKED_ROTATION`, `OPENCODE_TRANSIENT_FAILOVER_BACKOFF`) and does not double-skip when combined with them; direct accounts have a null proxy key and correctly record nothing. Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thanks @maxmad64bis!
…zapw#13643) The Codex executor now whitelists the wire `reasoning` object to `effort`/`summary` before dispatch instead of spreading whatever the client sent, and maps `reasoning.enabled === false` to `effort: "none"` when no more specific effort was requested. OpenRouter-style keys (`enabled`, `max_tokens`, `exclude`) were reaching the Responses API and 400-ing the whole combo target with `Unknown parameter: 'reasoning.<key>'`. The precedence chain keeps an explicit per-request effort ahead of `enabled: false`, and the strip matches the siblings already removed in the same function (`truncation`, `user`, `prompt_cache_retention`). Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thanks @HouMinXi!
…iegosouzapw#13560) Two related fixes: the combo health probe sends `reasoning_effort: "none"` for Gemini-family models so the probe budget is not spent on thinking, and `detectMalformedNonStream` stops classifying a response with `finish_reason` `length`/`tool_calls`/`content_filter` and empty content as `empty_choices`. The second half is the important one: it brings the post-translation check in line with `isEmptyContentResponse` (`open-sse/services/errorClassifier.ts`, `LEGIT_EMPTY_OPENAI_FINISH`), which already treated those finish reasons as legitimate. Until now a response could pass the pre-translation check and still be rewritten into a synthetic 502 afterwards — for every non-streaming completion, not just combo probes. Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thanks @HouMinXi!
Rebuild fork's release/v3.8.51 off the latest upstream tip so the deployed Dokploy compose stays compatible and the repository tracks the current release. The upstream-only changes are carried verbatim; the fork's local build/deploy additions (webpack + heap, dokploy compose, traefik TLS) are reapplied as a single commit on top.
…uzapw#13829) The provider-scoped /api/v1/providers/:provider/models route filtered unified-catalog rows only by the internal provider node ID. For compatible provider nodes the catalog emits the node's public prefix in owned_by, so the endpoint returned an empty list even when the connection was active and synced models existed. Resolve the compatible node's prefix and accept it (alongside the internal id and alias) when filtering owned_by and when stripping the prefix from returned unprefixed model ids. Closes upstream issue diegosouzapw#13829.
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.
Summary
Brings the fork's
release/v3.8.51branch fully up to date with the upstream active release branch, then reapplies the fork's local deployment changes and the playground empty-models fix on top:release/v3.8.51tip (ce98c30cf), capturing hundreds of fixes/features; the fork's own build/deploy and fix commits sit cleanly on top (work in progress: that's thechore/update-fork-to-v3.8.51branch).docker-compose.dokploy.yml— Dokploy single-host compose: Redis sidecar, Traefik TLS labels, ports, volumes, healthcheck, memory args (OMNIROUTE_USE_TURBOPACK=0,OMNIROUTE_BUILD_WORKERS=1,OMNIROUTE_BUILD_MEMORY_MB=8192) for the 7.6 GB / 19 GB-swap VPS..dokploy/README.md— auto-deploy note.#13829):src/app/api/v1/providers/[provider]/models/route.ts— resolve the compatible node's public prefix and accept it (alongside the internal ID/alias) when filteringowned_byand when stripping the prefix from returned provider-scoped IDs. This makes the provider-scoped models endpoint return the synced imported models (e.g.hz/<model>, unprefixed) that were previously dropped, which is the same dataset the Playground dropdown reads.tests/unit/provider-models-v1-route.test.ts— RED→GREEN regression test.The resulting content diff vs the upstream tip is exactly 4 paths (compose + README + route + test), 169 insertions / 1 deletion.
Related Issues
Validation
Change type: provider / routing.
node --import tsx/esm --test tests/unit/provider-models-v1-route.test.tsnpm run check:any-budget:t11✅check-docs-sync/check-tracked-artifacts✅npm run typecheck:core:compile✅--pass-on-unpruned-suppressions(repo's ESLint is failing on a pre-existing environment issue with the react rule; the suppressions file is untouched)docker-compose.dokploy.ymlat HEAD is byte-identical to the currently deployed compose on the VPSTests Added Or Updated
tests/unit/provider-models-v1-route.test.ts— added the#13829regression test: seeds provider node (ID + prefixhz), active connection, and a synced imported model, asserts the unified catalog exposeshz/<model>withowned_by: hz, and that/v1/providers/{internal-id}/modelsreturns it unprefixed withowned_by: hz.Coverage Notes
The new test covers the exact regression scenario (node prefix resolution,
owned_bymatching, prefix stripping). No production code beyond the route changed.Reviewer Notes
release/v3.8.51branch. Once this branch is merged intorelease/v3.8.51, Dokploy will automatically build and deploy the new version (the compose file at HEAD matches the deployed one, so no config drift).v3.8.50-era fork) is untouched; reverting to it is a Dokploy side switch away.${JWT_SECRET}etc. from Dokploy's environment, already configured.