Skip to content

feat(providers): Models tab, cap cooldowns, Cursor catalog seeds - #35

Closed
OnlineChef wants to merge 66 commits into
devfrom
feat/provider-models-tab
Closed

feat(providers): Models tab, cap cooldowns, Cursor catalog seeds#35
OnlineChef wants to merge 66 commits into
devfrom
feat/provider-models-tab

Conversation

@OnlineChef

@OnlineChef OnlineChef commented Jul 31, 2026

Copy link
Copy Markdown

Summary

  • Provider workspace Models tab: per-model enable/disable via existing PUT /api/model-visibility (same path as the global Models page).
  • Sync models button forces rediscovery (POST /api/sync + reload), with All on/off bulk controls.
  • Each chip shows 30d usage (tokens · requests) from the already-loaded /api/usage rows.

Stacked on the en/nl i18n fix branch so main typechecks.

Test plan

  • cd gui && bunx tsc -b — 0 errors
  • Open Providers → OpenCode Go → Models: toggles flip models on/off
  • Sync models refreshes the chip list
  • Chips with traffic show token/request usage; quiet models stay without a usage line
  • Chip click still copies the model id

View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

Greptile Summary

This change adds provider model visibility controls, bulk actions, synchronization, and usage indicators, while limiting the GUI locale list to English and Dutch.

Three reproduced failures make the change unsafe to merge: a rejected single-model update leaves the switch in its optimistic state, bulk actions skip search-hidden and over-cap models, and users with saved German, Japanese, Korean, Russian, or Chinese preferences are silently moved to another language.

Confidence Score: 2/5

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for the posted P1 finding on the focused ProviderModels failure-path test, including both the test source and the execution output.
  • T-Rex produced a proof for the posted P1 finding on the focused ProviderModels bulk-toggle runtime test, including the test source and the execution output.
  • T-Rex produced a proof for the posted P1 finding on the runtime locale probe work, including the locale detection source, locale probe output, Playwright browser capture source, and the browser behavior videos before and after locale removal.
  • T-Rex produced a general-contract-validation-proof showing that runtime outputs cover all requested cases with exit code 0 and that Chromium captures reflect pre-change locale preservation and current options.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Removed locales discard existing locale preferences and browser-language selection

    • Bug
      • detectInitial returns en for stored de, ja, ko, ru, and zh when the browser is en-US; with no stored value it returns nl for de-DE, ja-JP, ko-KR, ru-RU, and zh-CN. LOCALES contains only nl,en.
    • Cause
      • Locale, DICTS, the stored-locale allowlist, the browser-locale branches, and locale options were reduced to English and Dutch in gui/src/i18n/shared.ts:5-26.
    • Fix
      • Restore supported locale dictionaries/options and recognition of stored and browser locales, or migrate stored values intentionally before removing language support.

    T-Rex Ran code and verified through T-Rex

Fix All in Cursor Fix All in Codex Fix All in Claude Code Fix All in Conductor

Prompt To Fix All With AI
### Issue 1
gui/src/components/provider-workspace/ProviderModels.tsx:211-223
**Failed toggles retain stale state**

A rejected visibility update leaves the optimistic `disabledNamespaced` change in place. `applyVisibility` reports the HTTP or network error but neither restores the prior switch state nor refreshes the catalog, so a model can appear disabled even though the server retained it as enabled.

### Issue 2
gui/src/components/provider-workspace/ProviderModels.tsx:246-250
**Bulk toggles omit hidden models**

The All on/off action submits `visibleModels`, not the provider's complete model catalog. That list is narrowed by the active search and capped at 300 rendered entries, so filtered-out models and model 301 onward keep their old visibility setting while the UI presents the operation as provider-wide.

### Issue 3
gui/src/i18n/shared.ts:5-26
**Existing locale preferences are discarded**

Removing `de`, `ja`, `ko`, `ru`, and `zh` from the locale registry makes existing stored preferences invalid. Those users fall back to English or Dutch, and the language picker no longer offers their selected language; browsers using those languages also default to Dutch.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(gui): provider Models tab toggles, ..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Summary by CodeRabbit

  • New Features
    • Added provider cooldown tracking for weekly and inference limits, with automatic expiry, optional provider pausing, and clear reset-time warnings.
    • Added model visibility controls, bulk enable/disable actions, catalog refresh, availability counts, and 30-day usage details.
    • Added Cursor catalog entries for Kimi, Claude thinking variants, and Gemini.
  • Improvements
    • Provider dashboards and navigation now highlight capped or paused providers.
    • Supported interface languages are now English and Dutch, with updated translations and Dutch cap-related messaging.
  • Documentation
    • Clarified that local development requires Bun on the system path.

ingwannu (Ingwannu) and others added 30 commits July 22, 2026 07:34
Init posthog-js only when VITE_POSTHOG_KEY is set; capture hashchange pageviews without identify/PII.

Co-authored-by: Cursor <cursoragent@cursor.com>
Greptile P1 on PR #2: window.location.href leaked the full URL (OAuth
?code=, invitation tokens, emails in query/hash) to PostHog as
$current_url. Report origin + pathname plus only a known hash route;
drop the query string and any unknown hash contents.
OmniRoute (open-source OpenAI-compatible free-model gateway, 250+ providers /
90+ free) wired into the opencodex provider registry as an OpenAI-compatible
provider (reuses the existing adapter kind):
- src/providers/registry.ts: OmniRoute entry (baseUrl https://api.omniroute.online/v1,
  configurable via OCX_OMNIROUTE_BASE_URL for self-hosted fleet instances; auth
  via OCX_OMNIROUTE_KEY), representative free model ids (kimi/glm/deepseek/qwen…).
- gui: provider icon + icon mapping so it appears in the provider rail.
- tests: provider-registry-parity updated for the new entry.
- docs/providers/omniroute.md: setup (key, optional self-hosted Docker), model
  selection, note that live /v1/models is the source of truth.

Additive — no change to existing providers. Local typecheck blocked by a
missing bun-types devDep in this worktree (env only; reproduces without this
change); CI verifies.

Co-authored-by: OnlineChef <280567955+OnlineChef@users.noreply.github.com>
…e-pool max) (#4)

* feat(codex): add rotation-mode + request-pacing config types

Foundation for multi-account free-pool maximization (opt-in, backward-compat):
- codexRotationMode: 'failover' (default, current sticky behavior) | 'round-robin'
  (new conversations rotate across usable pool accounts so a multi-account free
  pool multiplies throughput; thread affinity preserved; cooldown/reauth skipped).
- codexRequestPacing: jittered per-account inter-request delay [minMs,maxMs],
  off by default, so a multi-account pool never emits a regular ban-prone pattern.

Types only this commit; wiring follows. (Agent run aborted before impl.)

* feat(codex): round-robin pool rotation + jittered request pacing

Wire the opt-in multi-account free-pool maximization (types in prior commit):
- routing.ts: when codexRotationMode === 'round-robin', NEW (non-affined)
  conversations rotate across getEligiblePoolAccounts via a per-process cursor,
  so a multi-account free pool multiplies throughput instead of only failing
  over. Cooldown/reauth/soft-avoid already filtered; thread affinity preserved;
  activeCodexAccountId untouched; single account = no-op.
- pacer.ts: per-account jittered inter-request gap [minMs,maxMs] (defaults
  150-900ms), off by default (codexRequestPacing.enabled). Per-account state so
  concurrent accounts desync instead of a fixed ban-prone cadence.
- responses.ts: await codexPaceBeforeSend before the outbound Codex pool send
  (guarded by usesCodexForwardPoolAuth), no-op when disabled.

Tests (bun): 5 pacer + 5 rotation, all pass. typecheck clean. Backward-compat
(defaults preserve current failover + no-pacing behavior).

---------

Co-authored-by: OnlineChef <280567955+OnlineChef@users.noreply.github.com>
Record upstream, org fork, npm package, and the verified host pin for
OnlineChefGroep so ocx upgrades stay explicit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Without prompt=select_account an already-signed-in browser re-approves
the same JWT sub, so multiauth updates the active row instead of appending.

Co-authored-by: Cursor <cursoragent@cursor.com>
… + status endpoint

- types.ts: OcxClaudeDesktopProfile에 appliedFingerprint/appliedAt 추가
- desktop-3p.ts: writeDesktop3pConfig에 SHA-256 fingerprint 반환 + metadata write 실패 시 .bak rollback
- management-api.ts: /api/claude-desktop/apply에 fingerprint 저장 + /api/claude-desktop/status 엔드포인트 (saved-vs-on-disk 비교)
- claude-messages.ts: Desktop 3P alias 감지 시 logCtx.surface = 'claude-desktop'
- request-log.ts: surface 타입 확장 ('claude' | 'claude-desktop')
- usage/log.ts, usage/summary.ts: surface 필터 확장
- types.ts: desktopAutoApply 옵션 추가
- desktop-health.ts: in-memory 요청/에러 트래커 (NEW)
- claude-messages.ts: Desktop 요청 시 recordDesktopRequest() 호출
- management-api.ts: /api/claude-desktop/status에 health 포함 + autoApplyDesktopBestEffort (provider 변경 시 자동 3P config 갱신)
- management-api.ts: buildClaudeDesktopState에 effortSupported 추가
- ClaudeDesktop.tsx: status bar (applied/stale/not-applied) + health (lastRequest, req/err) + effort badge
- i18n 4개 언어: status/health/effort 키 추가
- styles.css: .claude-status-bar, .claude-effort-badge 스타일
…arch 260722)

- desktop-3p.ts: prefer1m:true 설정 (supports1m 모델에, 공식 스키마 정렬)
- desktop-3p.ts: isClaudeShapedId() 가드 추가 (Ollama '0 usable' 거부 방어)
- Luna 5레인 연구 기반: 공식 inferenceModels 스키마, 커뮤니티 모델 소실 이슈
- Add nl.ts translations + Dutch pages (Instellingen, Modellen, Systeem, Verkeer)
- Refactor App.tsx into provider-workspace shell
- Add quota bars + use-provider-quotas hook
- Extend de/en i18n with new keys
- Add depas.css styling, provider-quota and provider-workspace-shell CSS
- Update FLEET.md
… ja/ru

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
- App: re-apply saved theme on mount instead of wiping data-theme
- App: route legacy deep links (#codex-auth/#api/#claude/#combos/#subagents)
  through a per-page sub-target so bookmarks open the right tab/section
- App/Modellen/Systeem/Verkeer: drive all visible copy through t() and add
  the backing keys to every locale dict (en/nl/de/ko/zh/ru/ja)
- management-api: return non-OK when a single-provider quota probe yields no
  report so the GUI can show the refresh-failure state

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…ional divergence

Two upstream source-contract tests were merged into this fleet-pin branch but
never matched its deliberately divergent implementation, so CI's Test step was
red before this change (both already failed at parent f55fcb5).

- oauth-tos-warning: this host intentionally drops "cursor" from the elevated
  ToS-risk set (documented patch in gui/src/oauth-tos-risk.ts) so the warning
  modal never blocks the multi-account Cursor login flow. Assert cursor is
  unmarked instead of "elevated"; keep github-copilot as the elevated case.
- provider-workspace-rail: App.tsx here localizes the shell to Dutch page ids
  and models routing as { page, target } via readRouteFromHash/canonicalHash,
  not the upstream hashBelongsToPage/"providers/workspace" helper. Assert the
  branch's actual canonicalization + subroute-target threading contract.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…new routing code

The GUI lint step was failing on the deep-link/localization work from 05d8801:

- react-refresh/only-export-components: move readTheme/applyTheme/Theme out of the
  Instellingen component file into a dedicated gui/src/theme.ts util module (matching
  the existing format-bytes.ts / formatUptime.ts convention); import from App + Settings.
- react-hooks/set-state-in-effect: replace the target-sync effects in Modellen, Systeem
  and Verkeer with React's documented render-phase "adjust state when a prop changes"
  pattern, preserving the deep-link tab/section open behavior.
- react-hooks/refs: update pausedRef inside an effect instead of during render in Verkeer.
- local-i18n/no-hardcoded-ui-strings: route the remaining receipt-row/detail literals
  (tok, s, status, upstream:, id) through t() and add vk.rowTokens/rowDuration/detailStatus/
  detailUpstream/detailId to all seven locale dicts.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…a dedup, tab reset, routing tests

- Instellingen: track theme in React state so the active theme re-renders on select
- i18n/shared: keep English browser locales (en-*) instead of defaulting them to Dutch
- use-provider-quotas: dedupe in-flight requests by URL so a forced refresh (?refresh=1) never joins/loses to a non-forced one
- Modellen: reset the active tab to the default when the routed target is absent/invalid
- Extract hash routing into gui/src/route.ts (parseHash/canonicalHash) and rewrite the rail routing test to exercise real parse/canonicalize behavior

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
chore(fleet): reconcile fleet pin with main
docs: document maintainers and review ownership
…low-providers

feat(providers): add Tencent and SiliconFlow providers
feat(gui): optional PostHog EU analytics (CHE-785)
… repair, cursor fixes)

# Conflicts:
#	src/server/management-api.ts
# Conflicts:
#	gui/src/App.tsx
#	gui/src/i18n/de.ts
#	gui/src/i18n/en.ts
#	gui/src/i18n/ja.ts
#	gui/src/i18n/ko.ts
#	gui/src/i18n/ru.ts
#	gui/src/i18n/zh.ts
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Comment on lines +211 to +223
if (!response.ok) {
setActionOk(false);
setActionStatus(t("models.saveFailed"));
return;
}
setActionOk(true);
setActionStatus(t("models.applied"));
setCatalogEpoch(epoch => epoch + 1);
onRetryModels?.();
} catch {
setActionOk(false);
setActionStatus(t("models.networkError"));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed toggles retain stale state

A rejected visibility update leaves the optimistic disabledNamespaced change in place. applyVisibility reports the HTTP or network error but neither restores the prior switch state nor refreshes the catalog, so a model can appear disabled even though the server retained it as enabled.

Artifacts

Focused ProviderModels failure-path test source

  • Rendered component harness that starts with the model switch enabled and exercises rejected HTTP PUT and thrown network PUT failures, asserting the optimistic off state remains and no catalog reload occurs.

Focused ProviderModels failure-path test output

  • Executed Bun test output from /home/user/repo/gui with exit code 0; records the exact before/after switch state, request sequence, status messages, and both passing failure-path cases.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: gui/src/components/provider-workspace/ProviderModels.tsx
Line: 211-223

Comment:
**Failed toggles retain stale state**

A rejected visibility update leaves the optimistic `disabledNamespaced` change in place. `applyVisibility` reports the HTTP or network error but neither restores the prior switch state nor refreshes the catalog, so a model can appear disabled even though the server retained it as enabled.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

Comment on lines +246 to +250
const bulkToggle = async (enable: boolean) => {
if (bulkBusy || busyId || fetching || visibleModels.length === 0) return;
setBulkBusy(true);
await applyVisibility(visibleModels, enable);
setBulkBusy(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Bulk toggles omit hidden models

The All on/off action submits visibleModels, not the provider's complete model catalog. That list is narrowed by the active search and capped at 300 rendered entries, so filtered-out models and model 301 onward keep their old visibility setting while the UI presents the operation as provider-wide.

Artifacts

Focused ProviderModels bulk-toggle runtime test source

  • Authored rendered component test that exercises search-filtered and 301-model All off actions; it captures the outgoing visibility request targets and proves models outside the filtered or 300-item visible subset are omitted.

Focused ProviderModels bulk-toggle runtime test execution output

  • Captured Bun test output from /home/user/repo/gui showing both filtered and over-300 request-target assertions passed: 2 pass, 0 fail, exit code 0.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: gui/src/components/provider-workspace/ProviderModels.tsx
Line: 246-250

Comment:
**Bulk toggles omit hidden models**

The All on/off action submits `visibleModels`, not the provider's complete model catalog. That list is narrowed by the active search and capped at 300 rendered entries, so filtered-out models and model 301 onward keep their old visibility setting while the UI presents the operation as provider-wide.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

Comment thread gui/src/i18n/shared.ts Outdated
Comment on lines 5 to 26
export type Locale = "en" | "nl";
export type { TKey };

export const DICTS: Record<Locale, Record<TKey, string>> = { en, nl, de, ko, zh, ru, ja };
export const DICTS: Record<Locale, Record<TKey, string>> = { en, nl };

export const LOCALES: { code: Locale; name: string; htmlLang: string }[] = [
{ code: "nl", name: "Nederlands", htmlLang: "nl" },
{ code: "en", name: "English", htmlLang: "en" },
{ code: "de", name: "Deutsch", htmlLang: "de" },
{ code: "ko", name: "한국어", htmlLang: "ko" },
{ code: "zh", name: "中文", htmlLang: "zh-CN" },
{ code: "ru", name: "Русский", htmlLang: "ru" },
{ code: "ja", name: "日本語", htmlLang: "ja" },
];

const LANG_KEY = "ocx-lang";

export function detectInitial(): Locale {
try {
const stored = localStorage.getItem(LANG_KEY);
if (stored === "en" || stored === "nl" || stored === "de" || stored === "ko" || stored === "zh" || stored === "ru" || stored === "ja") return stored;
if (stored === "en" || stored === "nl") return stored;
} catch { /* ignore */ }
const nav = typeof navigator !== "undefined" ? navigator.language.toLowerCase() : "nl";
if (nav.startsWith("en")) return "en";
if (nav.startsWith("de")) return "de";
if (nav.startsWith("ko")) return "ko";
if (nav.startsWith("zh")) return "zh";
if (nav.startsWith("ru")) return "ru";
if (nav.startsWith("ja")) return "ja";
// ChefGroep host build: Joep-facing copy is Dutch by default (DESIGN.md voice-kit).
// The language switcher in the sidebar still offers English and the rest.
// The language switcher in the sidebar still offers English.
return "nl";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Existing locale preferences are discarded

Removing de, ja, ko, ru, and zh from the locale registry makes existing stored preferences invalid. Those users fall back to English or Dutch, and the language picker no longer offers their selected language; browsers using those languages also default to Dutch.

Artifacts

Runtime probe source for stored and browser locale detection

  • Authored executable probe exercises stored de, ja, ko, ru, zh and browser de-DE, ja-JP, ko-KR, ru-RU, zh-CN against the current locale code; it defines the exact runtime cases.

Runtime locale probe output

  • Executed `bun trex-artifacts/pr35-locale-runtime-check.ts` from `/home/user/repo` with exit code 0; it shows every removed stored locale falling back to en, every tested non-English browser locale selecting nl, and options nl,en.

Playwright Chromium browser capture source

  • Authored Playwright Chromium capture script loads the locale harness with stored de and records observed detection and options; it drives the before and after recordings.

▶ Browser behavior before locale removal

  • Playwright Chromium recording of the parent revision with stored de and browser en-US; it shows that the prior locale is retained and remains selectable.

▶ Browser behavior after locale removal

  • Playwright Chromium recording of the current revision with stored de and browser en-US; it shows the stored locale is rejected and absent from options.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: gui/src/i18n/shared.ts
Line: 5-26

Comment:
**Existing locale preferences are discarded**

Removing `de`, `ja`, `ko`, `ru`, and `zh` from the locale registry makes existing stored preferences invalid. Those users fall back to English or Dutch, and the language picker no longer offers their selected language; browsers using those languages also default to Dutch.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Conductor

OnlineChef and others added 8 commits July 31, 2026 00:16
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…kspace keys

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…models

Persist hard-cap cooldowns (Clinepass INFERENCE_CAP etc.), expose them in /api/config for the Providers UI, and seed kimi-k3 / gemini-3.6-flash / thinking variants in the Cursor static catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
Record/expire weekly inference caps on the startServer config object so
routing and /api/config see disables without restart, tighten hard-cap
detection, and keep GUI messages short.
@OnlineChef OnlineChef changed the title [WRONG BRANCH] feat(gui): provider Models tab — on/off, fetch, usage per model feat(providers): Models tab, cap cooldowns, Cursor catalog seeds Jul 31, 2026
@OnlineChef
OnlineChef changed the base branch from main to dev July 31, 2026 01:11
@github-actions github-actions Bot added the enhancement New feature or request label Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
gui/src/components/provider-workspace/ProviderRail.tsx (1)

76-95: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Capped rows lose their underlying status in the accessible name.

Line 79 replaces status entirely with t("pws.capCooldown.badge") ("Capped") whenever capped is true. That status value is the only thing surfaced to non-sighted/non-mouse users (aria-label at line 95, title at line 134) — the disabled/needs-attention/needs-setup semantics that statusLabel(item, t) normally conveys are silently dropped for a capped-and-disabled or capped-and-needs-setup provider. Sighted users still get both signals (group heading + the separate "Capped" pill at lines 114-116), so this is a real information-loss gap specifically for assistive-tech users.

🔧 Proposed fix: combine rather than replace
-  const status = capped ? t("pws.capCooldown.badge") : statusLabel(item, t);
+  const baseStatus = statusLabel(item, t);
+  const status = capped ? `${baseStatus} · ${t("pws.capCooldown.badge")}` : baseStatus;

Also applies to: 134-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/components/provider-workspace/ProviderRail.tsx` around lines 76 - 95,
Update the status construction near statusLabel in the provider rail row so
capped providers retain their underlying status while also exposing the
cap-cooldown label, combining both labels with the existing accessible
separator. Ensure the combined status is used by both the aria-label and title
paths, including capped providers that are disabled or need setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 120-129: Update the release version checks in the workflow’s
branch-selection logic to enforce the complete channel formats: main releases
must match stable x.y.z semver with no prerelease, and preview releases must
match x.y.z-preview.* without any preceding prerelease identifier. Replace the
broad wildcard checks around RELEASE_VERSION with exact validation consistent
with the contract enforced by the release tooling, and obtain the required
security review for this workflow change.

In `@gui/src/components/provider-workspace/ProviderModels.tsx`:
- Around line 81-92: Update the usageById memoization logic so the unconditional
map.set(key, row) does not overwrite the guarded higher-token selection when key
equals bare. Only set the key separately when it differs from bare, while
preserving the higher-token preference for bare-id collisions and namespaced
lookups.

In `@gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx`:
- Around line 51-78: Update the attention-item construction around
buildAttentionItems and cooldownOverrides so providers present in
cappedProviders are excluded from the generic attention list. Reuse the existing
cap-provider identity data to filter or suppress those entries while preserving
attention items for uncapped providers and the dedicated Usage caps rendering.

In `@gui/src/pages/providers-shared.ts`:
- Around line 18-30: Replace the duplicated ProviderCapCooldown declaration in
the provider configuration types with a shared or generated type derived from
the server-side cap-cooldown definition, preserving the required source field
and all wire-shape fields. Update the providerCooldowns typing to use that
shared type so future server changes cannot silently diverge.

In `@gui/src/styles/provider-workspace-shell.css`:
- Around line 371-392: Replace the deprecated word-break declaration in
.pws-cap-cooldown-msg with the modern overflow-wrap property, preserving the
existing behavior of wrapping long unbreakable cooldown message text.

In `@src/adapters/cursor/discovery.ts`:
- Around line 227-234: Add CURSOR_MODEL_EFFORT_TIERS mappings for every model
marked supportsReasoningEffort in the discovery list, including kimi-k3 and the
newly advertised Claude variants, so cursorEffortSuffix() produces valid tier
suffixes. Update the kimi-model bare-ID exclusion logic in discovery.ts so
kimi-k3 is no longer treated as a model that must remain bare; preserve bare
handling for other applicable Kimi models.

In `@src/providers/cap-cooldown.ts`:
- Around line 87-129: Update recordProviderCapCooldown to mutate the shared live
OcxConfig used by request handling instead of independently loading a fresh
config snapshot via loadConfig. Ensure providerCooldowns and
providers[providerName].disabled are applied to that live reference before
saveConfigPreservingClaudeCode persists it, preserving the existing cooldown and
disable behavior.

In `@tests/cap-cooldown.test.ts`:
- Around line 1-36: Extend the cap-cooldown test suite to cover
recordProviderCapCooldown and expireProviderCooldowns, using the same config
object and load/save path used by production. Assert that recording a cooldown
is visible after round-tripping through
loadConfig/saveConfigPreservingClaudeCode, and verify expiration removes
eligible cooldowns while preserving valid ones.

In `@tests/helpers/shipped-locales.ts`:
- Around line 5-13: The shipped-locale helper must derive SHIPPED_LOCALES from
the runtime DICTS source instead of hard-coding en and nl; update
tests/helpers/shipped-locales.ts lines 5-13 to reuse DICTS while preserving
resolved dictionary values. Update tests/provider-payload.test.ts line 213 to
iterate over the shared SHIPPED_LOCALES helper/map rather than a literal [en,
nl] list.

---

Outside diff comments:
In `@gui/src/components/provider-workspace/ProviderRail.tsx`:
- Around line 76-95: Update the status construction near statusLabel in the
provider rail row so capped providers retain their underlying status while also
exposing the cap-cooldown label, combining both labels with the existing
accessible separator. Ensure the combined status is used by both the aria-label
and title paths, including capped providers that are disabled or need setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8d380c6c-053b-48b7-9e2c-e1f35196f44e

📥 Commits

Reviewing files that changed from the base of the PR and between ae6efe6 and 599b1c1.

⛔ Files ignored due to path filters (1)
  • gui/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (43)
  • .github/workflows/issue-triage.yml
  • .github/workflows/release.yml
  • docs-site/src/content/docs/contributing.md
  • gui/src/components/QuotaBars.tsx
  • gui/src/components/provider-workspace/ProviderDetails.tsx
  • gui/src/components/provider-workspace/ProviderModels.tsx
  • gui/src/components/provider-workspace/ProviderOverview.tsx
  • gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
  • gui/src/components/provider-workspace/ProviderRail.tsx
  • gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
  • gui/src/formatUptime.ts
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/nl.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/shared.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/Providers.tsx
  • gui/src/pages/providers-shared.ts
  • gui/src/styles/provider-overview-dashboard.css
  • gui/src/styles/provider-workspace-shell.css
  • gui/tests/apikeys-layout.test.ts
  • gui/tests/claude-auth-mode-badge.test.ts
  • gui/tests/claude-desktop-locale.test.ts
  • gui/tests/grok-page.test.ts
  • gui/tests/helpers/locales.ts
  • gui/tests/provider-model-custom-add.test.tsx
  • gui/tests/subagents-classic.test.ts
  • gui/tests/usage-grok-filter.test.ts
  • gui/tests/usage-layout.test.ts
  • src/adapters/cursor/discovery.ts
  • src/providers/cap-cooldown.ts
  • src/server/auth-cors.ts
  • src/server/management/config-routes.ts
  • src/server/request-log.ts
  • tests/cap-cooldown.test.ts
  • tests/codex-auth-modal-status.test.ts
  • tests/dashboard-uptime.test.ts
  • tests/helpers/shipped-locales.ts
  • tests/oauth-tos-warning.test.ts
  • tests/provider-payload.test.ts
💤 Files with no reviewable changes (5)
  • gui/src/i18n/ko.ts
  • gui/src/formatUptime.ts
  • gui/src/components/QuotaBars.tsx
  • gui/src/i18n/zh.ts
  • gui/src/i18n/de.ts

Comment thread gui/src/components/provider-workspace/ProviderModels.tsx
Comment thread gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
Comment thread gui/src/pages/providers-shared.ts
Comment thread gui/src/styles/provider-workspace-shell.css
Comment thread src/adapters/cursor/discovery.ts Outdated
Comment thread src/providers/cap-cooldown.ts
Comment thread tests/cap-cooldown.test.ts
Comment thread tests/helpers/shipped-locales.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
gui/src/components/provider-workspace/ProviderRail.tsx (1)

76-95: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Capped rows lose their underlying status in the accessible name.

Line 79 replaces status entirely with t("pws.capCooldown.badge") ("Capped") whenever capped is true. That status value is the only thing surfaced to non-sighted/non-mouse users (aria-label at line 95, title at line 134) — the disabled/needs-attention/needs-setup semantics that statusLabel(item, t) normally conveys are silently dropped for a capped-and-disabled or capped-and-needs-setup provider. Sighted users still get both signals (group heading + the separate "Capped" pill at lines 114-116), so this is a real information-loss gap specifically for assistive-tech users.

🔧 Proposed fix: combine rather than replace
-  const status = capped ? t("pws.capCooldown.badge") : statusLabel(item, t);
+  const baseStatus = statusLabel(item, t);
+  const status = capped ? `${baseStatus} · ${t("pws.capCooldown.badge")}` : baseStatus;

Also applies to: 134-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/components/provider-workspace/ProviderRail.tsx` around lines 76 - 95,
Update the status construction near statusLabel in the provider rail row so
capped providers retain their underlying status while also exposing the
cap-cooldown label, combining both labels with the existing accessible
separator. Ensure the combined status is used by both the aria-label and title
paths, including capped providers that are disabled or need setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 120-129: Update the release version checks in the workflow’s
branch-selection logic to enforce the complete channel formats: main releases
must match stable x.y.z semver with no prerelease, and preview releases must
match x.y.z-preview.* without any preceding prerelease identifier. Replace the
broad wildcard checks around RELEASE_VERSION with exact validation consistent
with the contract enforced by the release tooling, and obtain the required
security review for this workflow change.

In `@gui/src/components/provider-workspace/ProviderModels.tsx`:
- Around line 81-92: Update the usageById memoization logic so the unconditional
map.set(key, row) does not overwrite the guarded higher-token selection when key
equals bare. Only set the key separately when it differs from bare, while
preserving the higher-token preference for bare-id collisions and namespaced
lookups.

In `@gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx`:
- Around line 51-78: Update the attention-item construction around
buildAttentionItems and cooldownOverrides so providers present in
cappedProviders are excluded from the generic attention list. Reuse the existing
cap-provider identity data to filter or suppress those entries while preserving
attention items for uncapped providers and the dedicated Usage caps rendering.

In `@gui/src/pages/providers-shared.ts`:
- Around line 18-30: Replace the duplicated ProviderCapCooldown declaration in
the provider configuration types with a shared or generated type derived from
the server-side cap-cooldown definition, preserving the required source field
and all wire-shape fields. Update the providerCooldowns typing to use that
shared type so future server changes cannot silently diverge.

In `@gui/src/styles/provider-workspace-shell.css`:
- Around line 371-392: Replace the deprecated word-break declaration in
.pws-cap-cooldown-msg with the modern overflow-wrap property, preserving the
existing behavior of wrapping long unbreakable cooldown message text.

In `@src/adapters/cursor/discovery.ts`:
- Around line 227-234: Add CURSOR_MODEL_EFFORT_TIERS mappings for every model
marked supportsReasoningEffort in the discovery list, including kimi-k3 and the
newly advertised Claude variants, so cursorEffortSuffix() produces valid tier
suffixes. Update the kimi-model bare-ID exclusion logic in discovery.ts so
kimi-k3 is no longer treated as a model that must remain bare; preserve bare
handling for other applicable Kimi models.

In `@src/providers/cap-cooldown.ts`:
- Around line 87-129: Update recordProviderCapCooldown to mutate the shared live
OcxConfig used by request handling instead of independently loading a fresh
config snapshot via loadConfig. Ensure providerCooldowns and
providers[providerName].disabled are applied to that live reference before
saveConfigPreservingClaudeCode persists it, preserving the existing cooldown and
disable behavior.

In `@tests/cap-cooldown.test.ts`:
- Around line 1-36: Extend the cap-cooldown test suite to cover
recordProviderCapCooldown and expireProviderCooldowns, using the same config
object and load/save path used by production. Assert that recording a cooldown
is visible after round-tripping through
loadConfig/saveConfigPreservingClaudeCode, and verify expiration removes
eligible cooldowns while preserving valid ones.

In `@tests/helpers/shipped-locales.ts`:
- Around line 5-13: The shipped-locale helper must derive SHIPPED_LOCALES from
the runtime DICTS source instead of hard-coding en and nl; update
tests/helpers/shipped-locales.ts lines 5-13 to reuse DICTS while preserving
resolved dictionary values. Update tests/provider-payload.test.ts line 213 to
iterate over the shared SHIPPED_LOCALES helper/map rather than a literal [en,
nl] list.

---

Outside diff comments:
In `@gui/src/components/provider-workspace/ProviderRail.tsx`:
- Around line 76-95: Update the status construction near statusLabel in the
provider rail row so capped providers retain their underlying status while also
exposing the cap-cooldown label, combining both labels with the existing
accessible separator. Ensure the combined status is used by both the aria-label
and title paths, including capped providers that are disabled or need setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8d380c6c-053b-48b7-9e2c-e1f35196f44e

📥 Commits

Reviewing files that changed from the base of the PR and between ae6efe6 and 599b1c1.

⛔ Files ignored due to path filters (1)
  • gui/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (43)
  • .github/workflows/issue-triage.yml
  • .github/workflows/release.yml
  • docs-site/src/content/docs/contributing.md
  • gui/src/components/QuotaBars.tsx
  • gui/src/components/provider-workspace/ProviderDetails.tsx
  • gui/src/components/provider-workspace/ProviderModels.tsx
  • gui/src/components/provider-workspace/ProviderOverview.tsx
  • gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
  • gui/src/components/provider-workspace/ProviderRail.tsx
  • gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
  • gui/src/formatUptime.ts
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/nl.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/shared.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/Providers.tsx
  • gui/src/pages/providers-shared.ts
  • gui/src/styles/provider-overview-dashboard.css
  • gui/src/styles/provider-workspace-shell.css
  • gui/tests/apikeys-layout.test.ts
  • gui/tests/claude-auth-mode-badge.test.ts
  • gui/tests/claude-desktop-locale.test.ts
  • gui/tests/grok-page.test.ts
  • gui/tests/helpers/locales.ts
  • gui/tests/provider-model-custom-add.test.tsx
  • gui/tests/subagents-classic.test.ts
  • gui/tests/usage-grok-filter.test.ts
  • gui/tests/usage-layout.test.ts
  • src/adapters/cursor/discovery.ts
  • src/providers/cap-cooldown.ts
  • src/server/auth-cors.ts
  • src/server/management/config-routes.ts
  • src/server/request-log.ts
  • tests/cap-cooldown.test.ts
  • tests/codex-auth-modal-status.test.ts
  • tests/dashboard-uptime.test.ts
  • tests/helpers/shipped-locales.ts
  • tests/oauth-tos-warning.test.ts
  • tests/provider-payload.test.ts
💤 Files with no reviewable changes (5)
  • gui/src/i18n/ko.ts
  • gui/src/formatUptime.ts
  • gui/src/components/QuotaBars.tsx
  • gui/src/i18n/zh.ts
  • gui/src/i18n/de.ts
🛑 Comments failed to post (1)
.github/workflows/release.yml (1)

120-129: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce the complete channel version format.

*-preview.* accepts 1.2.3-foo-preview.1, which is valid npm semver but violates the x.y.z-preview.* contract in scripts/release.ts:220-244. A manual dispatch can therefore bypass the release CLI and publish a misclassified preview. Validate both branch formats exactly.

Proposed fix
-              if [[ "$RELEASE_VERSION" == *-* ]]; then
+              if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                 echo "::error::main releases must use a stable semver version; got ${RELEASE_VERSION}"
                 exit 1
               fi
@@
-              if [[ "$RELEASE_VERSION" != *-preview.* ]]; then
+              if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-preview\.[[:alnum:]_.]+$ ]]; then
                 echo "::error::preview releases must use a preview prerelease version; got ${RELEASE_VERSION}"
                 exit 1
               fi

As per path instructions, workflow changes require explicit security review per MAINTAINERS.md.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                echo "::error::main releases must use a stable semver version; got ${RELEASE_VERSION}"
                exit 1
              fi
              ;;
            refs/heads/preview)
              expected_tag="preview"
              if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-preview\.[[:alnum:]_.]+$ ]]; then
                echo "::error::preview releases must use a preview prerelease version; got ${RELEASE_VERSION}"
                exit 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 120 - 129, Update the release
version checks in the workflow’s branch-selection logic to enforce the complete
channel formats: main releases must match stable x.y.z semver with no
prerelease, and preview releases must match x.y.z-preview.* without any
preceding prerelease identifier. Replace the broad wildcard checks around
RELEASE_VERSION with exact validation consistent with the contract enforced by
the release tooling, and obtain the required security review for this workflow
change.

Source: Path instructions

OnlineChef and others added 3 commits July 31, 2026 01:38
The refreshed Cursor seed marked kimi-k3 and the five claude `-thinking` ids as
supportsReasoningEffort without effort-map tiers, so the catalog's bidirectional
invariant failed. kimi-k3 gets its verified low/high/max ladder; the `-thinking`
ids are complete wire ids with no verified suffix ladder, so they lose the flag
and are sent bare like claude-opus-4-7-fast.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Review follow-ups: the bare-id usage map no longer overwrites its own higher-token
pick, capped providers are excluded from the generic attention list now that they
have a dedicated Usage caps section, .pws-cap-cooldown-msg uses overflow-wrap, and
the Cursor seed comment reflects kimi-k3's verified tiers.

The shipped-locale registry moves to a React-free gui/src/i18n/dicts.ts (re-exported
by shared.ts) so tests/helpers/shipped-locales.ts derives from DICTS instead of a
hand-maintained { en, nl } list, and tests/provider-payload.test.ts iterates that
helper.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…json

The cap-cooldown path auto-disables a provider but relied on `GET /api/config`
to ever undo it, wrote config.json once per rejected request, and attributed
the pause to a display label rather than the routed provider key.

- Expiry now runs on a sweep timer bound to the live config, so a headless
  proxy (the primary mode) re-enables a capped provider at its reset instead
  of staying disabled until the dashboard is opened or the proxy restarts.
  Routing reads `providers[].disabled` directly, so this was a hard outage.
- An already-active cooldown is authoritative: repeat hard-cap 429s no longer
  rewrite `until` and re-save. Upstream countdowns are hour-quantized, so only
  a materially longer window (a 24h cap escalating to weekly) replaces it.
- An explicit `disabled` PATCH releases the cooldown's ownership of the flag,
  so the expiry sweep cannot re-enable a provider an operator turned off.
- Cap attribution uses the new `logCtx.providerConfigKey` (the raw routed
  config key) instead of `logCtx.provider`, which carries a Codex account
  suffix (`chatgpt-work`) that can collide with a real provider of that name.
- `GET /api/config` uses the static imports the module already has.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/server/management/provider-routes.ts (1)

179-189: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Release cooldown ownership only after the patch is validated.

Line 187 mutates config.providerCooldowns[name].disabledProvider to false immediately, but the request can still fail after this point. The rejecting branches are providerManagementConfigError (Line 252), providerDestinationResolvedError (Line 254 and Line 274), and the canonical-openai guard (Line 257). Each returns 400 before config.providers[name] is assigned at Line 282.

Failure mode: the PATCH returns 400 and the provider keeps the disabled: true that the cap cooldown set, yet the cooldown entry no longer claims ownership of that flag. When the window ends, expireProviderCooldowns (src/providers/cap-cooldown.ts Lines 99-101) deletes the entry without clearing disabled, because entry.disabledProvider is now false. The provider stays disabled after the cap expires, with no cooldown record left to explain it, and the operator must re-enable it by hand. A disabled-only patch never reaches those branches, but a combined patch such as { "disabled": false, "baseUrl": "…" } with an invalid baseUrl, or an openai re-enable that fails the DNS gate, does.

Move the release next to the persisted write so it runs only on the path that actually applies the operator decision.

🐛 Proposed fix — defer the ownership release to the save path
     if (Object.hasOwn(rawBody, "disabled")) {
       if (typeof rawBody.disabled !== "boolean") return jsonResponse({ error: "disabled must be a boolean" }, 400);
       if (rawBody.disabled && name === config.defaultProvider) {
         return jsonResponse({ error: "cannot disable the default provider; set another default first" }, 400);
       }
       next.disabled = rawBody.disabled;
-      // An explicit operator decision outranks the cap cooldown: without this the expiry
-      // sweep would re-enable a provider the operator just turned off by hand.
-      releaseProviderCooldownDisableOwnership(config, name);
       touched = true;
     }

Then release it where the change is committed:

     const { saveConfigPreservingClaudeCode: save } = await import("../../config");
+    // An explicit operator decision outranks the cap cooldown: without this the expiry
+    // sweep would re-enable a provider the operator just turned off by hand. Run it here,
+    // after every validation gate, so a rejected patch cannot strip cooldown ownership.
+    if (Object.hasOwn(rawBody, "disabled")) {
+      releaseProviderCooldownDisableOwnership(config, name);
+    }
     config.providers[name] = stripRegistryOnlyStaticHeaders(name, next);
     save(config);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/provider-routes.ts` around lines 179 - 189, Move
releaseProviderCooldownDisableOwnership from the initial disabled-field handling
to the successful persistence path immediately alongside the
config.providers[name] assignment. Ensure it executes only after all validation
and rejection branches, including providerManagementConfigError,
providerDestinationResolvedError, and the canonical-openai guard, have passed;
retain the existing disabled-field validation and touched tracking.
gui/src/components/provider-workspace/ProviderModels.tsx (3)

457-465: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Give each copy button a model-specific accessible name.

aria-label replaces the button's visible model ID in the accessible name. Every copy button therefore exposes the same label, such as Copy model ID. A screen-reader user cannot distinguish which model will be copied.

Add localized keys with a {model} placeholder for both the normal and copied states.

Suggested change
-                  aria-label={copied ? t("pws.modelCopied") : t("pws.copyModelId")}
+                  aria-label={copied
+                    ? t("pws.modelCopiedFor", { model: modelId })
+                    : t("pws.copyModelIdFor", { model: modelId })}

As per coding guidelines, files under src/components must not hardcode visible UI text; use i18n keys and add each new key to every locale module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/components/provider-workspace/ProviderModels.tsx` around lines 457 -
465, Update the copy button in ProviderModels to use model-specific localized
accessible names, passing modelId to new translation keys with a {model}
placeholder for both normal and copied states. Add the corresponding keys to
every locale module and preserve the existing state-dependent copied versus copy
behavior.

Source: Coding guidelines


252-259: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Synchronize local state after bulk visibility changes.

bulkToggle does not update local visibility state. applyVisibility only starts an asynchronous /api/models reload, and that reload can fail while retaining the old state. bulkBusy is then cleared immediately. The chips can show the old visibility and accept a second action based on stale state after the server accepted the bulk update.

Apply the same optimistic update and rollback used for individual changes. Include selected-model state if modelVisible uses it.

Suggested bulk state transaction
   setBulkBusy(true);
-  await applyVisibility(models, enable);
+  const previousDisabled = disabledNamespaced;
+  setDisabledNamespaced(prev => {
+    const nextSet = new Set(prev);
+    for (const id of models) {
+      const namespaced = nativeIds.has(id) ? id : `${item.name}/${id}`;
+      if (enable) {
+        nextSet.delete(id);
+        nextSet.delete(namespaced);
+      } else {
+        nextSet.add(namespaced);
+      }
+    }
+    return nextSet;
+  });
+  const saved = await applyVisibility(models, enable);
+  if (!saved) setDisabledNamespaced(previousDisabled);
   setBulkBusy(false);

As per path instructions, GUI state changes must stay consistent with management API responses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/components/provider-workspace/ProviderModels.tsx` around lines 252 -
259, Update bulkToggle to use the same optimistic state transaction as
individual visibility changes: immediately update local model visibility and any
selected-model state used by modelVisible, await applyVisibility, and roll back
those updates if the management API operation fails. Keep bulkBusy active until
the transaction completes so chips cannot render or accept actions from stale
state.

Source: Path instructions


193-210: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep local model visibility state consistent with the server call.

ProviderModels.tsx:193-199 computes chip state from selectedModels, while toggleModel only updates disabledNamespaced. For non-native models, modelIncluded returns false when selectedMap[provider] exists and does not include the model, so enabling an unselected model leaves the switch off until onRetryModels refreshes selectedModels. Add a local selected-model override for this optimistic path, and roll it back when applyVisibility(...).ok is false.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/components/provider-workspace/ProviderModels.tsx` around lines 193 -
210, Update the local visibility state used by isModelOn/modelIncluded so
optimistic toggles also reflect selectedModels changes for non-native models. In
toggleModel, apply the selected-model override alongside disabledNamespaced
before calling applyVisibility, and roll back that override when applyVisibility
returns false, preserving the server-refreshed state on success.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gui/src/i18n/shared.ts`:
- Around line 2-5: Update the locale preference loading and validation flow
associated with Locale and DICTS so saved de, ja, ko, ru, and zh values are
migrated to documented supported locales before runtime validation. Persist each
migrated value, preserve en and nl behavior, and add regression tests covering
every legacy saved preference; do not rely solely on DICTS-derived tests.

In `@src/adapters/cursor/effort-map.ts`:
- Around line 48-49: Add focused regression tests in
tests/cursor-effort-suffix.test.ts for the new "kimi-k3" mapping in
GetUsableModels: assert the exact supported ladder is low, high, and max, and
verify representative Cursor request suffix conversions for those tiers,
including that unsupported medium/xhigh tiers are not accepted.

In `@src/providers/cap-cooldown.ts`:
- Around line 61-70: Update resolveProviderConfigKey to check config.providers
with the repository’s existing own-property helper, hasOwnProvider, instead of
direct truthiness lookup before returning logProvider. Preserve the existing
prefix-matching behavior while ensuring inherited keys such as constructor,
toString, and valueOf cannot resolve as provider configuration keys.

In `@tests/cap-cooldown.test.ts`:
- Around line 127-150: Make the repeat hard-cap test count actual persistence
calls rather than a manual counter increment. Extend
RecordProviderCapCooldownOpts and recordProviderCapCooldown to accept an
optional save callback, invoke it only when a save is genuinely required, and
pass that callback from the test so the 51 records assert exactly one save while
preserving the existing cooldown assertions.

---

Outside diff comments:
In `@gui/src/components/provider-workspace/ProviderModels.tsx`:
- Around line 457-465: Update the copy button in ProviderModels to use
model-specific localized accessible names, passing modelId to new translation
keys with a {model} placeholder for both normal and copied states. Add the
corresponding keys to every locale module and preserve the existing
state-dependent copied versus copy behavior.
- Around line 252-259: Update bulkToggle to use the same optimistic state
transaction as individual visibility changes: immediately update local model
visibility and any selected-model state used by modelVisible, await
applyVisibility, and roll back those updates if the management API operation
fails. Keep bulkBusy active until the transaction completes so chips cannot
render or accept actions from stale state.
- Around line 193-210: Update the local visibility state used by
isModelOn/modelIncluded so optimistic toggles also reflect selectedModels
changes for non-native models. In toggleModel, apply the selected-model override
alongside disabledNamespaced before calling applyVisibility, and roll back that
override when applyVisibility returns false, preserving the server-refreshed
state on success.

In `@src/server/management/provider-routes.ts`:
- Around line 179-189: Move releaseProviderCooldownDisableOwnership from the
initial disabled-field handling to the successful persistence path immediately
alongside the config.providers[name] assignment. Ensure it executes only after
all validation and rejection branches, including providerManagementConfigError,
providerDestinationResolvedError, and the canonical-openai guard, have passed;
retain the existing disabled-field validation and touched tracking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 23d9bf0a-b4b3-4375-80e9-0ba7c92c7c33

📥 Commits

Reviewing files that changed from the base of the PR and between 599b1c1 and 3a1e4c3.

📒 Files selected for processing (19)
  • gui/AGENTS.md
  • gui/src/components/provider-workspace/ProviderModels.tsx
  • gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
  • gui/src/i18n/dicts.ts
  • gui/src/i18n/shared.ts
  • gui/src/styles/provider-workspace-shell.css
  • src/adapters/cursor/discovery.ts
  • src/adapters/cursor/effort-map.ts
  • src/providers/cap-cooldown.ts
  • src/server/auth-cors.ts
  • src/server/index.ts
  • src/server/management/config-routes.ts
  • src/server/management/provider-routes.ts
  • src/server/request-log.ts
  • src/server/responses/core.ts
  • src/types.ts
  • tests/cap-cooldown.test.ts
  • tests/helpers/shipped-locales.ts
  • tests/provider-payload.test.ts

Comment thread gui/src/i18n/shared.ts
Comment on lines +2 to +5
import { DICTS, type Locale, type TKey } from "./dicts";

export type Locale = "en" | "de" | "ko" | "zh" | "ru" | "ja";
export type { TKey };

export const DICTS: Record<Locale, Record<TKey, string>> = { en, de, ko, zh, ru, ja };
export type { Locale, TKey };
export { DICTS };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Migrate legacy locale preferences before narrowing the runtime locale set.

The runtime now accepts only en and nl. Existing users with saved de, ja, ko, ru, or zh preferences will silently switch to the Dutch fallback after upgrade.

Either retain the previous locale registrations or map each legacy value to a documented supported locale before validation. Persist the migrated value and add regression tests for saved legacy values. The DICTS-derived tests do not cover this path.

The supplied locale change details confirm that validation and detection for the removed locales were deleted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/i18n/shared.ts` around lines 2 - 5, Update the locale preference
loading and validation flow associated with Locale and DICTS so saved de, ja,
ko, ru, and zh values are migrated to documented supported locales before
runtime validation. Persist each migrated value, preserve en and nl behavior,
and add regression tests covering every legacy saved preference; do not rely
solely on DICTS-derived tests.

Comment thread src/adapters/cursor/effort-map.ts Outdated
Comment on lines +48 to +49
// GetUsableModels exposes kimi-k3-{low,high,max} (no medium/xhigh tier).
"kimi-k3": ["low", "high", "max"],

@coderabbitai coderabbitai Bot Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add focused regression coverage for the new kimi-k3 suffixes.

The new mapping changes Cursor request construction, but the shown tests/cursor-effort-suffix.test.ts cases cover other ladders only. Add assertions for the exact ladder and representative suffix conversions.

Suggested test coverage
+test("maps kimi-k3 effort tiers to Cursor suffixes", () => {
+  expect(cursorModelEffortLadder("kimi-k3")).toEqual(["low", "high", "max"]);
+  expect(cursorEffortSuffix("kimi-k3", "low")).toBe("low");
+  expect(cursorEffortSuffix("kimi-k3", "max")).toBe("max");
+});

As per path instructions, a behavior change in src/ must include focused regression coverage in tests/.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adapters/cursor/effort-map.ts` around lines 48 - 49, Add focused
regression tests in tests/cursor-effort-suffix.test.ts for the new "kimi-k3"
mapping in GetUsableModels: assert the exact supported ladder is low, high, and
max, and verify representative Cursor request suffix conversions for those
tiers, including that unsupported medium/xhigh tiers are not accepted.

Source: Path instructions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already covered: tests/cursor-effort-suffix.test.ts:69-77 asserts cursorModelEffortLadder("kimi-k3") equals ["low","high","max"] plus the low/high/max/bare suffix conversions and that medium clamps to high rather than being accepted literally.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

Comment on lines +61 to +70
/** Map log labels like `openai-work` back to a config.providers key. */
export function resolveProviderConfigKey(config: OcxConfig, logProvider: string): string | null {
if (!logProvider || logProvider === "combo") return null;
if (config.providers[logProvider]) return logProvider;
const names = Object.keys(config.providers).sort((a, b) => b.length - a.length);
for (const name of names) {
if (logProvider.startsWith(`${name}-`)) return name;
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use an own-property check in resolveProviderConfigKey.

Line 64 reads config.providers[logProvider] directly. providers is a plain Record, so inherited keys such as constructor, toString, or valueOf return a truthy value. resolveProviderConfigKey then returns that name as a valid config key. In recordProviderCapCooldown the guard on Line 217 is !!config.providers[key], which is also truthy for the same inherited keys, so Line 222 would write disabled = true onto an inherited object instead of a real provider, and Line 233 would persist a providerCooldowns entry for a provider that does not exist.

The repository already uses an own-property helper for this class of key on config.providers (hasOwnProvider in src/server/management/provider-routes.ts Line 132). Apply the same check here so this exported function cannot resolve an inherited key.

🛡️ Proposed hardening
 export function resolveProviderConfigKey(config: OcxConfig, logProvider: string): string | null {
   if (!logProvider || logProvider === "combo") return null;
-  if (config.providers[logProvider]) return logProvider;
+  if (Object.hasOwn(config.providers, logProvider)) return logProvider;
   const names = Object.keys(config.providers).sort((a, b) => b.length - a.length);
   for (const name of names) {
     if (logProvider.startsWith(`${name}-`)) return name;
   }
   return null;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Map log labels like `openai-work` back to a config.providers key. */
export function resolveProviderConfigKey(config: OcxConfig, logProvider: string): string | null {
if (!logProvider || logProvider === "combo") return null;
if (config.providers[logProvider]) return logProvider;
const names = Object.keys(config.providers).sort((a, b) => b.length - a.length);
for (const name of names) {
if (logProvider.startsWith(`${name}-`)) return name;
}
return null;
}
/** Map log labels like `openai-work` back to a config.providers key. */
export function resolveProviderConfigKey(config: OcxConfig, logProvider: string): string | null {
if (!logProvider || logProvider === "combo") return null;
if (Object.hasOwn(config.providers, logProvider)) return logProvider;
const names = Object.keys(config.providers).sort((a, b) => b.length - a.length);
for (const name of names) {
if (logProvider.startsWith(`${name}-`)) return name;
}
return null;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/cap-cooldown.ts` around lines 61 - 70, Update
resolveProviderConfigKey to check config.providers with the repository’s
existing own-property helper, hasOwnProvider, instead of direct truthiness
lookup before returning logProvider. Preserve the existing prefix-matching
behavior while ensuring inherited keys such as constructor, toString, and
valueOf cannot resolve as provider configuration keys.

Comment on lines +127 to +150
test("repeat hard-cap 429s inside an active window neither rewrite nor re-save", () => {
const config = bareConfig();
const now = Date.UTC(2026, 6, 31, 0, 0, 0);
let saves = 0;
const save = () => { saves += 1; };
// Stand in for saveConfigPreservingClaudeCode by counting the writes the record path asks for.
const record = (at: number) => recordProviderCapCooldown(config, "cline-pass", 429, CAP_429, {
now: at,
save: false,
});

const first = record(now);
save();
expect(first?.until).toBe(now + (1 * 24 + 22) * HOUR_MS);

// A retrying client produces one of these per rejected request for the whole cap window.
for (let i = 1; i <= 50; i += 1) {
const again = record(now + i * 60_000);
expect(again?.until).toBe(first?.until);
expect(again?.recordedAt).toBe(now);
}
expect(saves).toBe(1);
expect(Object.keys(config.providerCooldowns ?? {})).toEqual(["cline-pass"]);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The saves assertion in this test cannot fail.

Line 133-136 passes save: false to every recordProviderCapCooldown call, so the module never invokes a save. The counter is incremented only by the local save() call at Line 139. The assertion at Line 148, expect(saves).toBe(1), therefore holds no matter what the record path does, and the test name "neither rewrite nor re-save" only verifies the "no rewrite" half through Lines 145-146.

Effect: a future regression that removes the active-cooldown short-circuit at src/providers/cap-cooldown.ts Line 213 would restore one saveConfigPreservingClaudeCode fsync per rejected request for the whole cap window, and this test would still pass.

RecordProviderCapCooldownOpts exposes only save?: boolean, so a real save count needs an injectable saver, the way startProviderCooldownSweep already accepts opts.save. Either extend the options to accept a save callback and count real invocations, or drop the counter so the test does not claim coverage it does not have.

💚 Option A — drop the misleading counter
   test("repeat hard-cap 429s inside an active window neither rewrite nor re-save", () => {
     const config = bareConfig();
     const now = Date.UTC(2026, 6, 31, 0, 0, 0);
-    let saves = 0;
-    const save = () => { saves += 1; };
-    // Stand in for saveConfigPreservingClaudeCode by counting the writes the record path asks for.
     const record = (at: number) => recordProviderCapCooldown(config, "cline-pass", 429, CAP_429, {
       now: at,
       save: false,
     });
 
     const first = record(now);
-    save();
     expect(first?.until).toBe(now + (1 * 24 + 22) * HOUR_MS);
 
     // A retrying client produces one of these per rejected request for the whole cap window.
     for (let i = 1; i <= 50; i += 1) {
       const again = record(now + i * 60_000);
       expect(again?.until).toBe(first?.until);
       expect(again?.recordedAt).toBe(now);
     }
-    expect(saves).toBe(1);
     expect(Object.keys(config.providerCooldowns ?? {})).toEqual(["cline-pass"]);
   });

Option B keeps the stronger guarantee: add an optional saver to RecordProviderCapCooldownOpts (mirroring startProviderCooldownSweep), pass the counter in, and assert exactly one real save across the 51 calls. Do you want me to prepare that change?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cap-cooldown.test.ts` around lines 127 - 150, Make the repeat hard-cap
test count actual persistence calls rather than a manual counter increment.
Extend RecordProviderCapCooldownOpts and recordProviderCapCooldown to accept an
optional save callback, invoke it only when a save is genuinely required, and
pass that callback from the test so the 51 records assert exactly one save while
preserving the existing cooldown assertions.

Source: Path instructions

OnlineChef and others added 3 commits August 1, 2026 13:47
The PR base was retargeted from main to dev, so the branch had to absorb 207
dev commits across 39 conflicted files. Resolutions, by area:

- i18n: the branch's en/nl-only registry wins, so de/ja/ko/ru/zh stay deleted.
  dev's api.workspace.* keys are kept (dev ships the components that use them);
  the branch's pws.capCooldown.* keys are kept alongside them. The
  "retired workspace keys stay removed" gates in gui/tests contradicted dev's
  shipped UI, so they now assert the keys *resolve* in every shipped locale,
  reading DICTS rather than raw module text (nl spreads en).

- Codex routing: dev's accountPoolStrategy engine (round-robin, fill-first,
  sticky limits, per-scope pools) supersedes the branch's single persisted
  rrCursor. The fork's codexRotationMode alias would have gone inert, so
  resolveCodexPoolStrategy() now honours it exactly as dev's own OcxConfig doc
  promises. tests/rotation-persist.test.ts only pinned the deleted file-
  persistence mechanism and is removed; the cursor is now process-local, which
  is dev's documented design. This is a behaviour change: the round-robin
  cursor no longer survives a restart.

- src/usage/*, src/telemetry/posthog-server.ts, src/providers/registry.ts:
  add/add conflicts where dev is a strict superset (window rolling, sample
  validation, longest-prefix pricing, the telemetry property allowlist and
  retry/backoff, the richer OmniRoute entry). Took dev.

- types.ts: kept dev's three new OAuth pool configs; dropped dev's duplicate
  budgets block, which the branch had already added earlier in the interface.

- Cursor catalog: union of dev's verified kimi-k3 note and the PR's new
  *-thinking and gemini-3.6-flash seeds. Also dropped a duplicate kimi-k3 key
  the auto-merge had silently introduced into effort-map.ts.

- release.yml: took dev's. dev's scripts/release.ts pins releaseBranch = "main"
  and derives the dist-tag from the version, so the branch's main/preview
  branch-exact guard would have contradicted the script that dispatches it.
  This touches release automation and needs a maintainer security review.

- ci.yml keeps all four jobs (dev's two plus the PR line's dependency audit and
  actionlint). tests/ci-workflows.test.ts hardcoded a two-job timeout count, so
  the bounded-job invariant now derives from the parsed job set instead.

- gui/package.json keeps both font deps (main.tsx imports jetbrains-mono,
  depas.css imports ibm-plex-mono) and bun.lock was regenerated.

Verified: bun run typecheck, gui tsc -b, lint:gui, lint:i18n, privacy:scan and
build:gui all clean. gui suite 405/405. Root suite 6265 pass; the only 3
failures (CLI subcommand help x2, service diagnostics) reproduce on a pristine
origin/dev checkout in the same sandbox and are environment-dependent.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…d locales

resolveProviderConfigKey read `config.providers[logProvider]` directly, so an
inherited member name like `constructor` or `toString` resolved as a provider
key: recordProviderCapCooldown would then set `disabled` on Object.prototype's
member and persist a providerCooldowns entry for a provider that does not
exist. It now uses an own-property check, matching `hasOwnProvider` in
src/server/management/provider-routes.ts.

The write-amplification test counted its own local save() call, so its
`expect(saves).toBe(1)` held regardless of what the record path did and would
not have caught a regression in the active-window short-circuit.
RecordProviderCapCooldownOpts.save now also accepts a saver function, mirroring
startProviderCooldownSweep, and the test injects the counter so the assertion
covers all 51 records.

detectInitial() dropped saved `de`/`ja`/`ko`/`ru`/`zh` preferences into the
Dutch host default. Those users had explicitly chosen a non-Dutch language, so
the retired codes now migrate to English and the migrated value is written back.
Browser-language detection is unchanged: Dutch stays the default for anything
that is not en*.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
The `react-doctor` PR gate (scope `changed`, blocking `warning`) failed with
one error and eight warnings on the pages this branch adds to `main`:

- `posthog-js@^1.407.0` resolved its range floor to a release Socket scores
  46/100 on the supply-chain axis, below the gate's minimum of 50. The lock
  already resolved 1.409.5 (56/100), so raising the floor to match keeps the
  installed tree identical and clears the error.
- `Instellingen` had an interactive overlay with no role, an unlabeled locale
  `<select>`, and two `<button>`s defaulting to `type="submit"`.
- `Systeem` read `res.json()` before checking status in the update-check,
  update-run, and sync handlers; they now use the existing `readJsonOrThrow`
  helper, which preserves the server's error message.
- `Verkeer` copied the log array just to sort it; `toSorted` matches the rest
  of the dashboard.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/ci.yml (3)

148-192: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Audit both dependency lockfiles in the security job.

.github/workflows/ci.yml installs and audits only the root bun.lock, while the test job also installs gui/bun.lock at lines 77-79. A high-severity finding in gui/bun.lock bypasses this CI security gate. Run the frozen install and bun audit --audit-level=high for both bun.lock directories, upload both JSON reports, and fail the job when either install/audit exits non-zero.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 148 - 192, Update the security job to
audit both the root lockfile and gui/bun.lock: perform frozen installs and
high-severity audits from each lockfile’s directory, capture separate JSON
reports, and upload both artifacts. Ensure the job records and enforces each
install/audit exit status so it fails when either dependency set cannot install
or has audit findings, while preserving the existing root audit behavior.

Source: Path instructions


194-203: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Trigger workflow linting for every workflow change.

The lint-github-actions job in .github/workflows/ci.yml only runs when the path filters match. Those filters list specific workflow files and omit .github/workflows/issue-triage.yml, so a PR that changes only that workflow can skip the actionlint check.

Use .github/workflows/** in both pull_request.paths and push.paths, or add a separate actionlint workflow with that trigger. Align any path-contract test for workflow changes with the same rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 194 - 203, Update the path filters
governing the lint-github-actions job so both pull_request.paths and push.paths
include the .github/workflows/** pattern, ensuring every workflow change
triggers actionlint. Update the related path-contract test to assert the same
workflow-wide rule.

Source: Path instructions


194-203: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin the actionlint tool version in .github/workflows/ci.yml.

The pinned reference raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 resolves to a wrapper whose version input defaults to latest, so a mutable rhysd/actionlint release can change this security gate without a workflow diff. Set an approved fixed actionlint version and verify its downloaded binary digest, or use a vendored immutable binary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 194 - 203, Update the “Lint workflows”
step in the lint-github-actions job to stop relying on the wrapper’s mutable
latest actionlint version. Configure an approved fixed actionlint version and
verify its downloaded binary digest, or replace the wrapper with an immutable
vendored actionlint binary while preserving the workflow linting behavior.

Sources: Path instructions, MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 148-192: Update the security job to audit both the root lockfile
and gui/bun.lock: perform frozen installs and high-severity audits from each
lockfile’s directory, capture separate JSON reports, and upload both artifacts.
Ensure the job records and enforces each install/audit exit status so it fails
when either dependency set cannot install or has audit findings, while
preserving the existing root audit behavior.
- Around line 194-203: Update the path filters governing the lint-github-actions
job so both pull_request.paths and push.paths include the .github/workflows/**
pattern, ensuring every workflow change triggers actionlint. Update the related
path-contract test to assert the same workflow-wide rule.
- Around line 194-203: Update the “Lint workflows” step in the
lint-github-actions job to stop relying on the wrapper’s mutable latest
actionlint version. Configure an approved fixed actionlint version and verify
its downloaded binary digest, or replace the wrapper with an immutable vendored
actionlint binary while preserving the workflow linting behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0b5a0edb-8404-4212-a4ec-274011e6cd35

📥 Commits

Reviewing files that changed from the base of the PR and between 3a1e4c3 and efce77d.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

OnlineChef and others added 4 commits August 2, 2026 00:05
Defer cooldown ownership release until after PATCH validation succeeds,
sync bulk model toggles with rollback, restore capped-rail status for
assistive tech, keep PostHog hashes on the De Pas routes, and audit
gui/bun.lock in CI.

refs #35
Resolve issue-triage prompt redirect and keep en/nl locale gates.

refs #35
The Security audit job now runs `bun audit --audit-level=high` in gui/ as
well, and the GUI tree resolved two high-severity transitive advisories:
brace-expansion 5.0.6 (GHSA-mh99-v99m-4gvg, GHSA-3jxr-9vmj-r5cp) via
minimatch and postcss 8.5.15 (GHSA-r28c-9q8g-f849) via vite.

Both are indirect, so pin them through `overrides` (the same mechanism the
root package.json already uses for fast-uri) rather than promoting them to
direct dependencies. The floors are the patched releases, so the lowest
version the range admits is not vulnerable.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Importing posthog.ts in bun tests loaded posthog-js, which crashes happy-dom
on EventTarget setup. Move the URL allowlist into a pure module.

refs #35

Copy link
Copy Markdown
Author

Closing without merge. This branch remains a code donor, but the 66-commit cross-cutting diff is no longer a safe review or rollback unit.

Reconstruct as separate PRs in this order:

  1. Provider model visibility: complete catalog, optimistic single/bulk transaction, rollback, selected-model consistency, accessibility, focused GUI tests.
  2. Cap cooldown core: own-property checks, live-config mutation, ownership release only after validation, real persistence-count tests.
  3. Cursor catalog/effort mapping: Cursor-only code and focused request tests.
  4. Locale policy: retain old locales or explicitly migrate and persist every legacy preference; no silent fallback.
  5. CI/release hardening: both lockfiles audited, all workflow changes actionlinted, pinned actionlint, exact channel semver, required expected SHA.
  6. Styling/PostHog only after the functional lanes.

Do not reopen this PR for incremental fixes; cherry-pick or reimplement only coherent pieces into small branches.

@OnlineChef OnlineChef closed this Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants