feat(coding-agent): add per-provider account selection strategies

CO
4h ago
+521-20

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.accountStrategy policy in models.json and Claude's anthropicSubscriptionProvider.accountStrategy in 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: false behavior 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-TypeScript oxlint, and git diff --check passed.
  • 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.accountStrategy in models.json for generic providers, or anthropicSubscriptionProvider.accountStrategy in settings.json for Claude; project settings override global settings.
  • fill-first follows stored account order; round-robin assigns 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: false behavior; 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.