diff --git a/client/src/components/cos/tabs/AgentCard.jsx b/client/src/components/cos/tabs/AgentCard.jsx index 50cf738c58..53df823882 100644 --- a/client/src/components/cos/tabs/AgentCard.jsx +++ b/client/src/components/cos/tabs/AgentCard.jsx @@ -23,7 +23,8 @@ import { GitPullRequest, Sparkles, RefreshCw, - Copy + Copy, + Target } from 'lucide-react'; import * as api from '../../../services/api'; import OutputBlocks from '../OutputBlocks'; @@ -104,6 +105,52 @@ function TranscriptTruncationNotice({ transcript }) { ); } +// Goal-fidelity verdict (#5994) — did this run build what the task asked for? +// Distinct from the quality reviewers, which never see the request. Rendered +// whatever the verdict: a `ship` is the evidence the gate ran, and its absence +// is what tells the reader the run was never judged against its objective. +// +// `missing` / `unrequested` are model-authored text derived from an untrusted +// diff, so they render as plain list items — never a link, path, or command. +const GOAL_FIDELITY_TONE = { + ship: { border: 'border-port-success/30', text: 'text-port-success', label: 'Delivers the objective' }, + 'fix-first': { border: 'border-port-warning/30', text: 'text-port-warning', label: 'Delivers it, with gaps' }, + rethink: { border: 'border-port-error/40', text: 'text-port-error', label: 'Does not deliver the objective' } +}; + +function GoalFidelityPanel({ review }) { + const tone = GOAL_FIDELITY_TONE[review?.verdict]; + if (!tone) return null; + return ( +
+
+
+ {review.missing?.length > 0 && ( +
+ Asked for but missing: + +
+ )} + {review.unrequested?.length > 0 && ( +
+ Not asked for: + +
+ )} + {review.evidence &&

{review.evidence}

} +
+ ); +} + export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, onRelaunch, completed, paused = false, liveOutput, durations, onFeedbackChange, remote, peerName }) { const [expanded, setExpanded] = useState(false); const [now, setNow] = useState(Date.now()); @@ -869,6 +916,8 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, )} + {agent.result?.goalFidelity && } + {completed && (agent.metadata?.taskSummary || agent.metadata?.malwareScan?.reportUrl) && (
diff --git a/client/src/components/cos/tabs/AgentCard.test.jsx b/client/src/components/cos/tabs/AgentCard.test.jsx index 6089ab6c18..f10443cd21 100644 --- a/client/src/components/cos/tabs/AgentCard.test.jsx +++ b/client/src/components/cos/tabs/AgentCard.test.jsx @@ -416,3 +416,46 @@ describe('AgentCard missing shell explanation', () => { expect(screen.queryByText('No shell')).not.toBeInTheDocument(); }); }); + +// #5994: the goal-fidelity verdict — whether the run built what the task asked +// for, which no quality reviewer can answer because none of them see the request. +describe('AgentCard goal fidelity', () => { + const withReview = (goalFidelity) => ({ ...agent, result: { ...agent.result, goalFidelity } }); + + it('names the missing and unrequested work behind a rethink verdict', () => { + render( + + + + ); + + expect(screen.getByText(/Does not deliver the objective/)).toBeInTheDocument(); + expect(screen.getByText('the retry backoff')).toBeInTheDocument(); + expect(screen.getByText('an unrelated logging refactor')).toBeInTheDocument(); + expect(screen.getByText('no tests were run')).toBeInTheDocument(); + }); + + it('shows a clean ship verdict, and renders nothing at all for a run the gate never judged', () => { + const { unmount } = render( + + + + ); + expect(screen.getByText(/Delivers the objective/)).toBeInTheDocument(); + unmount(); + + render( + + + + ); + expect(screen.queryByText(/Goal fidelity/)).not.toBeInTheDocument(); + }); +}); + diff --git a/client/src/components/settings/CodeReviewersTab.jsx b/client/src/components/settings/CodeReviewersTab.jsx index c405ec97b3..eaa7db426c 100644 --- a/client/src/components/settings/CodeReviewersTab.jsx +++ b/client/src/components/settings/CodeReviewersTab.jsx @@ -4,6 +4,7 @@ import toast from '../ui/Toast'; import Banner from '../ui/Banner'; import * as api from '../../services/api'; import ReviewerPicker from '../cos/ReviewerPicker'; +import GoalFidelityControls from './GoalFidelityControls'; import useReviewerModelOptions from '../../hooks/useReviewerModelOptions'; import { reviewerModelsFromDefaults, reviewerModelsToDefaults, reviewerEffortsFromDefaults, reviewerEffortsToDefaults } from '../../lib/reviewerModels'; import { @@ -39,6 +40,7 @@ export default function CodeReviewersTab() { const [reviewerEfforts, setReviewerEfforts] = useState({}); const [stopMode, setStopMode] = useState(DEFAULT_REVIEW_STOP_MODE); const [reviewerApplies, setReviewerApplies] = useState(false); + const [goalFidelity, setGoalFidelity] = useState({ enabled: true, backend: null, model: null, effort: null }); const [installed, setInstalled] = useState({}); const modelOptions = useReviewerModelOptions(); @@ -60,6 +62,14 @@ export default function CodeReviewersTab() { setReviewerEfforts(reviewerEffortsFromDefaults(defaults)); setStopMode(defaults.stopMode || DEFAULT_REVIEW_STOP_MODE); setReviewerApplies(defaults.reviewerApplies === true); + // `enabled` defaults ON, so an absent block must read as on — not as a + // stored `false` the next save would then persist. + setGoalFidelity({ + enabled: defaults.goalFidelity?.enabled !== false, + backend: defaults.goalFidelity?.backend || null, + model: defaults.goalFidelity?.model || null, + effort: defaults.goalFidelity?.effort || null, + }); setInstalled(defaults.installed && typeof defaults.installed === 'object' && !Array.isArray(defaults.installed) ? defaults.installed : {}); } else { setLoadError(true); @@ -90,6 +100,15 @@ export default function CodeReviewersTab() { reviewerApplies, ...reviewerModelsToDefaults(reviewerModels), ...reviewerEffortsToDefaults(reviewerEfforts), + // Absent keys are dropped rather than sent as null: the schema treats an + // absent scalar as "inherit", and persisting an explicit null would be a + // pin the resolver can't tell from a deliberate one. + goalFidelity: { + enabled: goalFidelity.enabled, + ...(goalFidelity.backend ? { backend: goalFidelity.backend } : {}), + ...(goalFidelity.model ? { model: goalFidelity.model } : {}), + ...(goalFidelity.effort ? { effort: goalFidelity.effort } : {}), + }, }; const ok = await api.updateSettings({ codeReview: payload }, { silent: true }) .then(() => true) @@ -155,6 +174,13 @@ export default function CodeReviewersTab() { }} /> + +