Merged for the half that is still live. The 42 locale catalogs were fixed upstream while this sat open — all 51 now carry `'<name>'` — but the TypeScript default at `featureFlagDefinitions.ts:625` still had the raw tag, so the Feature Flags card kept failing to compile wherever the default is the source. Against the current tip this PR lands exactly three files: that one-character fix, the changelog fragment, and your 135-line regression test, with zero locale files touched.
The analysis is what makes it worth keeping. `next-intl` parsing `<name>` as a rich-text tag, `FeatureFlagsGrid.tsx` rendering the description through plain `t()` with no tag element, and the result being `INVALID_MESSAGE: UNCLOSED_TAG` — the card showing the raw key instead of the description, in every language — is a failure mode that is easy to misread as a missing translation. Verifying it against `use-intl`'s `development` build, the one Turbopack dev mode actually loads, is the detail that makes the reproduction trustworthy. Wrapping in ICU single quotes matches what #12369 did for `ccOnboardingKeyPlaceholder`.
5/5 on the regression suite against the tip.
---
⚠️ base-red inherited: #12732.
Thanks @pacocartones — 18 more of yours merged today.
The diagnosis is the valuable part here: a verbatim English copy is invisible to every i18n gate — `check-ui-keys-coverage` counts it as covered, `sync-ui-keys` only backfills absent keys, and `check-ui-value-drift` only reacts to English values that change. That is exactly how five keys shipped in #10900 stayed English in 39 catalogs without anything noticing, and a static test asserting "not equal to the English value" is the right instrument for it.
Two things needed reconciling before merge, both pure drift from the time this sat open:
**Nine locales did not exist when you cut this branch** — el, et, ga, hr, lt, lv, mt, sl, sr. They arrived with the Recent Requests keys but not the topology legend ones, so your own first assertion failed on them. I filled the three keys from each catalog's existing approved translations of the same words (`common.active`, `common.recent`, `analytics.modelStatusError`) rather than a fresh translation pass, so the legend reads the same as the rest of that language's dashboard.
**Two cognates were being failed for being correct** — `hr.recentRequestsModel` and `sl.recentRequestsModel` are "Model", which is the right word in Croatian and Slovenian. Your `COGNATES` set already existed for exactly this (`es.topologyLegendError`), but the third assertion swept every locale without consulting it. It does now.
5/5 on the suite afterwards, and the diff stayed at the nine locale files plus the test — no collateral sync.
---
Validated in the consolidated worktree for this batch. `typecheck:core` clean, `check:dashboard-typecheck` OK, complexity and cognitive-complexity under baseline.
⚠️ base-red inherited: #12732 — and separately, `npm run i18n:check` reports 75 doc-translation drift entries across 35 files on the pure tip (40 of them `docs/reference/ENVIRONMENT.md`). That is the docs pipeline, untouched by this PR.
Thanks @pacocartones — 17 more of yours merged today.
`XP_REWARDS` documented `streak_bonus` and `badge_unlock` and the pipeline paid neither — closing that gap is right. The idempotency design carries it: `advanceStreak()` reporting `extended` only on the call that moves the record to today is what keeps a same-day repeat from paying twice.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Forwarding only an explicit custom-model DB override as `modelInfo.targetFormat` is the key distinction — it lets chat core's credential-aware resolution pick the live connection setting instead of the format baked into the node id at creation, which is exactly what #11884 was about. I rebaselined the integration test file for the new case.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Translations was the one audio route the combo-resolution fixes never reached, so the same combo name worked on `/v1/audio/transcriptions` and failed here. Following the #9382 shape rather than inventing a new one is the right call.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
A real accessibility hole, not a cosmetic one: for the whole loading window the page exposed nothing but the sidebar, which is exactly the "API Keys link does nothing" report. Reusing the `role="status" aria-live="polite"` container the other dashboard loading states already use keeps it consistent.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
A documented byte estimate should never come back negative, even where today's usage makes it harmless — `Math.max(0, …)` is the honest floor. Replacing the three hardcoded `data:image/jpeg;base64,` literals with the shared constant is byte-identical and removes three chances to drift.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Two tables with no retention path at all and 775 MB of a 1.1 GB database is a real operational failure. Tying them to the existing `retention.callLogs` window rather than inventing a knob is right, and the reasoning is what makes it safe: once `cleanupCallLogs` purges the row `last_correlation_id` points at, the node can never render again.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Correct: `state: "updated"` mapped to no `OrchState`, so the channel carried a value no consumer could interpret. Reading the row back only on the no-status path, and publishing nothing when no row matched, both match what the A2A side already does.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
The tell is convincing: `detectionPatterns` in the same function already treats a present stored value as authoritative, so the two halves of one object disagreed. Making `degradationMap` stored-authoritative-when-present is the smaller change and the consistent one.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
The asymmetry was the bug: `/generations` iterated every image-capable target while `/edits` only ever tried the first. Extracting the iteration into `runImageComboTargets` and proving `/generations` byte-identical before wiring `/edits` onto it is the right order. Missing credentials skipping rather than hard-401 now matches generations too.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Genuinely subtle: counting over `xp_audit_log` meant a 30-day prune silently redefined "lifetime" milestones as "last 30 days", so a key doing 900 req/month could never reach Token Consumer. A durable counter backfilled on migration, mirroring the `user_levels.total_xp` pattern, is the right shape. I renumbered the migration to 176 — 173 was taken by a migration that landed after you opened this — and synced the doc count, which the operator approved.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.
Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).
Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
Reconciled and merged. This branch was stacked on #12941, which has since landed, so it read as 1484 additions across 16 files and CONFLICTING. I merged the current `release/v3.8.51` into it rather than rewriting your branch: `open-sse/executors/opencode.ts` conflicted in six places where your side was a strict superset of the squashed #12941, and the tip had touched that file through nothing but #12941, so your side was taken whole. The PR now reads as its real 12 files, +839/-25.
The change itself is right: a flapping upstream 5xx aborting the whole agentic chain is exactly the case where rotating to the next healthy account is safe, and keeping it a separate arm from the 400-empty branch matters because that one has to clone-read the body while this one never touches it. Threading `correlationId` through so interleaved requests stay attributable — and never fabricating one when absent — is the right discipline.
Both geo-block regression suites pass alongside the new ones (43/43 across the five opencode test files), which is what proves the conflict resolution preserved #12941's behaviour.
I also tightened the batch's file-size rebaseline here: `src/sse/handlers/chat.ts` needed no bump at all (it lands at 2452, under its existing 2458 freeze) and `open-sse/executors/base.ts` needed only your +2. An earlier measurement had included a local prettier reformat that is not part of this branch.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the rest of this batch — zero conflicts between them.
- `typecheck:core` clean; `check:changelog-integrity` OK
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 86 focused assertions green across the batch's 10 unit test files, plus 16/16 on the v1 plugin option schema and 16/16 on the v2 option tests
- `check-file-size` rebaselined for this batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode`, landed on #13141). `open-sse/utils/stream.ts` was deliberately left frozen: it is already 3115 > 3098 on the pure tip with zero contribution from this batch.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and the `stream.ts` freeze above). None of them touch these diffs.
Thanks @maxmad64bis.
Scoping the lock to the MODEL rather than the connection is the right layer for a `400 "Model is unavailable"` — a multi-day upstream outage on one model should not darken the account. Good discipline on the two allowlist-adjacent changes: gating the `markAccountUnavailable` branch on `ruleScope === "model"` AND `status === 400` leaves every other status on its existing path, and deliberately not widening `FULL_TEXT_RULE_PROVIDERS` keeps the #10880 egress-bucketed 429 classification intact. Reading the cooldown from the rule instead of a literal at the call site is what makes it self-healing.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the rest of this batch — zero conflicts between them.
- `typecheck:core` clean; `check:changelog-integrity` OK
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 86 focused assertions green across the batch's 10 unit test files, plus 16/16 on the v1 plugin option schema and 16/16 on the v2 option tests
- `check-file-size` rebaselined for this batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode`, landed on #13141). `open-sse/utils/stream.ts` was deliberately left frozen: it is already 3115 > 3098 on the pure tip with zero contribution from this batch.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and the `stream.ts` freeze above). None of them touch these diffs.
Thanks @maxmad64bis.
Both halves are right, and shipping them together is justified: the ladder dropping `pipeline` first threw away the upstream answer while keeping a prompt it already had a copy of, and `resolvePreviousResponseState` rebuilds continuation history out of exactly that field. The detail-panel fix has to ride along because the size-limit placeholder is a non-empty string, so fixing the ladder alone would let it overwrite a good payload. Re-checking emptiness per side after reading is the actual bug — `responseBody` being one value for both sides is what let a provider payload show as the client response.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the rest of this batch — zero conflicts between them.
- `typecheck:core` clean; `check:changelog-integrity` OK
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 86 focused assertions green across the batch's 10 unit test files, plus 16/16 on the v1 plugin option schema and 16/16 on the v2 option tests
- `check-file-size` rebaselined for this batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode`, landed on #13141). `open-sse/utils/stream.ts` was deliberately left frozen: it is already 3115 > 3098 on the pure tip with zero contribution from this batch.
⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and the `stream.ts` freeze above). None of them touch these diffs.
Thanks @maxmad64bis.
Keeping the tail is the right direction — `sendPrompt` resolves with the stdout collected since the prompt was written and stderr is read for diagnostics after a failure, so the newest output is what callers actually use. The `[...output truncated...]` marker keeps it from being silent. Resetting `stderrBuffer` alongside `stdoutBuffer` fixes the subtler half: diagnostics for one prompt were carrying stale output from every earlier one.
Verified on the tree that actually ships — your branch merged onto the current tip, which already carries #13096: `appendCapped()` and `settle()` coexist cleanly and all 7 assertions across both ACP test files pass together.
I reformatted the changelog fragment to the `changelog.d` convention (`- **fix(scope):** …`) before merging — `check:changelog-integrity` rejects a fragment that does not start with a markdown bullet, which is the same gate your #13158 was about. Wording is yours, unchanged in substance.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
`acpManager` being a module-level singleton is what turns this from a per-call leak into unbounded growth — the `MaxListenersExceededWarning` at 11 is the visible symptom. Routing every outcome through one `settle()` is the right shape, and deleting the session on the child's own exit fixes the map growth that `getActiveSessions()`'s `alive` filter was hiding.
I reformatted the changelog fragment to the `changelog.d` convention (`- **fix(scope):** …`) before merging — `check:changelog-integrity` rejects a fragment that does not start with a markdown bullet, which is the same gate your #13158 was about. Wording is yours, unchanged in substance.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
`stop()` is the normal lifecycle for a `follow: true` stream, not an edge case, so the `signal.aborted` early return skipping `clearTimeout` leaked one armed timer per stop. Cancelling the reader on the early loop exit closes the second half.
I reformatted the changelog fragment to the `changelog.d` convention (`- **fix(scope):** …`) before merging — `check:changelog-integrity` rejects a fragment that does not start with a markdown bullet, which is the same gate your #13158 was about. Wording is yours, unchanged in substance.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
Right precedent — #7494 fixed exactly this for the sql.js adapter and the `node:sqlite` one never got the same treatment, even though it is the default driver whenever better-sqlite3 is unavailable. `/api/db-backups/import` opening a throwaway adapter per request makes it reachable.
I reformatted the changelog fragment to the `changelog.d` convention (`- **fix(scope):** …`) before merging — `check:changelog-integrity` rejects a fragment that does not start with a markdown bullet, which is the same gate your #13158 was about. Wording is yours, unchanged in substance.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
Correct: an `abort` listener registered on an already-aborted signal never fires, and `safeEnqueue` can't save it because enqueuing into an unread stream only buffers. The abort-later test earning its keep as a guard on the healthy path is the right instinct.
I reformatted the changelog fragment to the `changelog.d` convention (`- **fix(scope):** …`) before merging — `check:changelog-integrity` rejects a fragment that does not start with a markdown bullet, which is the same gate your #13158 was about. Wording is yours, unchanged in substance.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
`once` only detaches when exit actually fires, so a plugin trapping SIGTERM accumulated one listener and one timer per hook timeout. Keying idempotence on the child via a `WeakSet` is right — a second SIGKILL timer would only re-signal a corpse.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
Attaching `close`/`error` before subscribing closes the window where a resource is held with no live cleanup path, and the destroyed-socket re-check after the handshake covers the in-flight case. `write()` not throwing synchronously is exactly why the old `try/catch` never fired.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
`resolveUserApiKey()` keyed an uncapped Map on an id taken straight from the webhook body. The LRU's recency test is what keeps this from regressing into a clear-when-full cache.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
Real abuse vector: the bot-webhook branch reached `proxyChat()` — which mints an API key and spends upstream quota — with nothing proving the caller was Telegram. Fail-closed 503 when the secret is unset is the right default.
---
Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the other 13 PRs of this batch — zero merge conflicts between them.
- `typecheck:core` clean
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 71 focused assertions green across the 13 test files this batch adds or touches
⚠️ base-red inherited: #12732 — `Docs Gates (fast-path)`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098). None of them touch this diff.
Thanks @anhtahaylove — the root-cause write-up, the measured before/after numbers and the red-before-green proof on every one of these made the batch reviewable as a unit.
#12934 unioned customModels into getAllActiveSyncedModels() alongside the
dispatch-time readers it was actually fixing (#12597: getActiveSyncedCatalog,
reconcileProvidersWithActiveSyncedCatalog, getActiveProvidersWithSyncedModel).
getAllActiveSyncedModels() is not a dispatch reader. Its three consumers read it
as "what the provider's live sync reported": /v1/models feeds its synced-emission
loop (and has a separate custom-model pass right after, which owns the
specialty-registry dedupe, hidePaid and the vision overrides), /api/models uses it
to decide whether an exclusive-listing provider suppresses a static row, and
getSyncedAutoAliases derives tier aliases from it. Blurring custom rows into
"synced" made a custom embedding/rerank model on jina-ai come out as the alias
row `jina/<id>` (parentless primary) with the registry's canonical
`jina-ai/<id>` demoted to its child — the inverse of the identity every other
specialty model of that provider carries, and it also dropped the registry's
`dimensions`.
Drop the union there only; the dispatch trio keeps it, so #12597's tests and the
400-on-picker-model fix are untouched. Locked by a new case in
custom-models-live-catalog-12597.test.ts and by both jina cases in
models-catalog-route.test.ts ("does not duplicate imported/custom Jina specialty
models"), which encode the two identities side by side.
#12832 declared `idx_cl_request_provider ON call_logs(request_type, provider)`
inside SCHEMA_SQL. That block runs before ensureCallLogsColumns() heals a legacy
call_logs table, so on any lineage predating the request_type column the CREATE
INDEX aborted the whole schema exec with "no such column: request_type" and the
server never finished opening the database.
Move the index next to the other request_type/combo indexes in
ensureCallLogsColumns(), which runs after the ALTER TABLE healing (and is also
called on the in-memory path), so both fresh and upgraded databases get it.
Proven by tests/unit/db-core-init.test.ts, "legacy call_logs schemas are upgraded
before combo target indexes are created" — failing on the release tip, green now.
Rebased onto the release tip after #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.
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 (#13025) together, following the route every previous gateway entry took (#11786 seekai, #10987 logfare, #10668 tabitoken, #10531 freebuff, #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.
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.
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.
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.
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.
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.
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
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 #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 #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.
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 #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`.
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 #12746/#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 #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.
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.
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.
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.
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.
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.
* 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 #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 #12378
* 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 #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 #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 #12639
* chore(a2a): follow-ups changelog
Changelog fragment for the five items PR-C delivers from #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#12639