Problem
Follow-up to #3728, which gave the antigravity reviewer row a Model cell. agy validates the model/effort PAIR — gemini-3.1-pro has no medium tier — and the provider pickers already honor that via effortLevelsForProvider(provider, model) / antigravityModelEffortLevels. The reviewer row does not: ReviewerPicker's Effort cell reads reviewerEffortLevels('antigravity'), a static low|medium|high ladder with no knowledge of the pinned model.
So a user can pick gemini-3.1-pro + medium and PortOS stores, displays, and emits a pair agy will reject. pairReviewerModelsAndEfforts does not catch it either — it validates the effort against the static ladder, and only splits an effort SUFFIXED into the model id.
The gap predates #3728 in one form (the effort cell could already mismatch agy's own configured default model), but that change makes it reachable through ordinary UI picking.
Scope
client/src/components/cos/ReviewerPicker.jsx renderEffortCell: for antigravity, narrow the ladder with antigravityModelEffortLevels(models.get(token), <agy catalog>), falling back to the full ladder when the catalog is empty or the model is unset (the same null-means-fall-back contract that helper already has).
client/src/hooks/useReviewerModelOptions.js already fetches /api/providers; expose what the picker needs (either the raw agy catalog or a modelEffortLevels(reviewer, model) accessor) rather than making the picker fetch.
- Keep an out-of-ladder STORED value selectable, the way
EffortSelect already does, so a stale pin renders instead of silently reading as unset.
Out of scope
Server-side pair validation: cosValidation.js is a pure lib with no provider catalog, and the honest failure there (drop to the reviewer's own default) is already what normalizeReviewerEffort does for an out-of-ladder level.
Problem
Follow-up to #3728, which gave the
antigravityreviewer row a Model cell.agyvalidates the model/effort PAIR —gemini-3.1-prohas nomediumtier — and the provider pickers already honor that viaeffortLevelsForProvider(provider, model)/antigravityModelEffortLevels. The reviewer row does not:ReviewerPicker's Effort cell readsreviewerEffortLevels('antigravity'), a staticlow|medium|highladder with no knowledge of the pinned model.So a user can pick
gemini-3.1-pro+mediumand PortOS stores, displays, and emits a pair agy will reject.pairReviewerModelsAndEffortsdoes not catch it either — it validates the effort against the static ladder, and only splits an effort SUFFIXED into the model id.The gap predates #3728 in one form (the effort cell could already mismatch agy's own configured default model), but that change makes it reachable through ordinary UI picking.
Scope
client/src/components/cos/ReviewerPicker.jsxrenderEffortCell: forantigravity, narrow the ladder withantigravityModelEffortLevels(models.get(token), <agy catalog>), falling back to the full ladder when the catalog is empty or the model is unset (the same null-means-fall-back contract that helper already has).client/src/hooks/useReviewerModelOptions.jsalready fetches/api/providers; expose what the picker needs (either the raw agy catalog or amodelEffortLevels(reviewer, model)accessor) rather than making the picker fetch.EffortSelectalready does, so a stale pin renders instead of silently reading as unset.Out of scope
Server-side pair validation:
cosValidation.jsis a pure lib with no provider catalog, and the honest failure there (drop to the reviewer's own default) is already whatnormalizeReviewerEffortdoes for an out-of-ladder level.