feat(coding-agent): add per-provider account selection strategies
Summary
Add per-provider accountStrategy with cache-affine, fill-first, and round-robin options. The default preserves the existing HRW selection. This draft is stacked on #5053.
Part of code-yeongyu/oh-my-openagent#10146.
Root cause
Account selection had no explicit strategy setting: the generic credential pool and Claude subscription lane always used their existing affinity ordering. Users could not choose stored-order filling or distribute new sessions through a round-robin cursor while retaining each session's prompt-cache locality.
Fix
- Validate the generic
credentials.accountStrategypolicy inmodels.jsonand Claude'santhropicSubscriptionProvider.accountStrategyin settings, naming the field on invalid values. - Share strategy ordering across both lanes. Fill-first follows the stored user order; round-robin advances a process-local cursor for each new affinity key and remembers its selected account, including replacements after a block or headroom skip.
- Keep the inherited headroom preference for all strategies, preserve pins, resident Claude account preference, disabled Claude fallback, and legacy
affinity: falsebehavior when no explicit strategy is configured. - Document configuration and process-local round-robin lifetime. Failure classification and block lifetimes are unchanged.
Evidence
The same focused regression file was run through the coding-agent Vitest package runner, without live model requests.
- RED at parent head
61ae034991d3035168842eeb8863680d5f28d0e6: 20 failed, 23 passed, 43 total; exit 1. Failures show missing settings support and incorrect fill-first/round-robin selection. - GREEN with this implementation: 43 passed, 0 failed; exit 0.
- The regression exercises both selectors, strategy validation, project overrides, default HRW parity, stored ordering, round-robin distribution and retention, cleared-block and headroom replacements, pins, all-near-limit availability, legacy affinity behavior, and Claude resident/fallback behavior.
- Changed-file
oxfmt --check, changed-TypeScriptoxlint, andgit diff --checkpassed. - Broader tests, builds, and typechecking were not run under this task's capacity limits. The TypeScript language server is unavailable.
Evidence is held locally:
strategy-red.log sha256 11ea1cf5f0baec7a8c2835b70453dcbbf83568ecd120991764b667cc8816c136
strategy-green.log sha256 8dc86d309eccd2c19c48a10e74bacf1fee7ddf9639c11566377748b506539b42
Summary by cubic
Adds per-provider accountStrategy so new sessions can pick accounts by stored order or round-robin instead of the default cache-affine (HRW) ordering.
New Features
- Set
credentials.accountStrategyinmodels.jsonfor generic providers, oranthropicSubscriptionProvider.accountStrategyinsettings.jsonfor Claude; project settings override global settings. fill-firstfollows stored account order;round-robinassigns consecutive new sessions to consecutive accounts via a process-local cursor and remembers each session's chosen account.- All strategies keep headroom preference, pins, Claude's resident-account preference, disabled fallback, and legacy
affinity: falsebehavior; invalid values produce a config error naming the field. - Documents the strategies in the providers guide and changelog.
Still open: typed blocked-account surface
This PR does not deliver the typed RPC/desktop surface that says why an account is blocked, until when, and what to do. That is the one open item for account visibility. It is tracked on #4092 (blocked on #4992 and #4674) and starts as soon as those two merge.