feat(review): remove legacy mode and harden agent/codex pipeline

Drop legacy runtime paths and role assignments across backend/frontend, and add upgrade-safe DB migration for existing installs. This aligns config, docs, tests, and UI to the agent-first architecture with codex as the only alternate engine.
This commit is contained in:
jeffusion
2026-03-19 14:50:21 +08:00
committed by 路遥知码力
parent 5bb1c3a2d1
commit 1c0c9afd17
35 changed files with 1595 additions and 1102 deletions

View File

@@ -17,15 +17,12 @@ import { toast } from 'sonner';
// Engine-specific field visibility
// ---------------------------------------------------------------------------
type EngineMode = 'legacy' | 'agent' | 'codex';
type EngineMode = 'agent' | 'codex';
/** The engine selector field — always visible at the top. */
const ENGINE_FIELD = 'REVIEW_ENGINE';
/** Fields shared across legacy & agent (but NOT codex). */
const LEGACY_AGENT_FIELDS = new Set([
'CUSTOM_SUMMARY_PROMPT',
'CUSTOM_LINE_COMMENT_PROMPT',
const AGENT_SHARED_FIELDS = new Set([
'GLOBAL_PROMPT',
'REVIEW_WORKDIR',
'REVIEW_MAX_PARALLEL_RUNS',
@@ -65,10 +62,8 @@ function getVisibleFields(engine: EngineMode, fields: ConfigFieldDto[]): ConfigF
return fields.filter((f) => {
if (f.envKey === ENGINE_FIELD) return false; // rendered separately
switch (engine) {
case 'legacy':
return LEGACY_AGENT_FIELDS.has(f.envKey);
case 'agent':
return LEGACY_AGENT_FIELDS.has(f.envKey) || AGENT_ONLY_FIELDS.has(f.envKey);
return AGENT_SHARED_FIELDS.has(f.envKey) || AGENT_ONLY_FIELDS.has(f.envKey);
case 'codex':
return CODEX_FIELDS.has(f.envKey);
default:
@@ -82,7 +77,6 @@ function getVisibleFields(engine: EngineMode, fields: ConfigFieldDto[]): ConfigF
// ---------------------------------------------------------------------------
const ENGINE_OPTIONS: { value: EngineMode; label: string; description: string }[] = [
{ value: 'legacy', label: 'Legacy', description: '传统单次 LLM 审查' },
{ value: 'agent', label: 'Agent', description: '多代理编排深度审查' },
{ value: 'codex', label: 'Codex', description: 'Codex CLI 审查' },
];
@@ -105,7 +99,7 @@ export function ReviewConfigPage() {
const engine: EngineMode = useMemo(() => {
const val = localConfig[ENGINE_FIELD];
if (val === 'agent' || val === 'codex') return val;
return 'legacy';
return 'agent';
}, [localConfig]);
// Derived: review group and memory group from fetched data
@@ -231,13 +225,11 @@ export function ReviewConfigPage() {
const syntheticReviewGroup: ConfigGroupDto | null = reviewGroup
? {
...reviewGroup,
label: engine === 'codex' ? 'Codex 审查设置' : engine === 'agent' ? 'Agent 审查设置' : 'Legacy 审查设置',
label: engine === 'codex' ? 'Codex 审查设置' : 'Agent 审查设置',
description:
engine === 'codex'
? 'Codex CLI 审查引擎配置'
: engine === 'agent'
? '多代理编排审查引擎配置'
: '传统单次 LLM 审查引擎配置',
: '多代理编排审查引擎配置',
fields: visibleReviewFields,
}
: null;
@@ -327,7 +319,7 @@ export function ReviewConfigPage() {
</div>
</CardHeader>
<CardContent className="p-6 bg-zinc-950/20">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{ENGINE_OPTIONS.map((opt) => (
<button
key={opt.value}
@@ -377,7 +369,6 @@ export function ReviewConfigPage() {
/>
)}
{/* LLM Provider config — legacy & agent only */}
{engine !== 'codex' && (
<>
<ProviderList />

View File

@@ -10,14 +10,13 @@ import { fetchProviders, fetchRoles, setRole } from '@/services/llmProviderServi
import { ModelCombobox } from './ModelCombobox';
const ROLE_LABELS: Record<string, { label: string; desc: string }> = {
legacy: { label: 'Legacy 审查', desc: '基础的单次代码审查模式,速度快但分析较浅' },
planner: { label: '规划器 Planner', desc: '多阶段审查的第一步,负责分析上下文并分配任务' },
specialist: { label: '专家 Specialist', desc: '执行深度代码审查的主力模型,专注于发现具体问题' },
judge: { label: '评审 Judge', desc: '对专家的建议进行审核、合并和过滤,确保评论质量' },
embedding: { label: '嵌入 Embedding', desc: '用于向量化代码和注释,支持语义搜索 (Qdrant)' },
};
const ROLES = ['legacy', 'planner', 'specialist', 'judge', 'embedding'];
const ROLES = ['planner', 'specialist', 'judge', 'embedding'];
interface RoleState {
providerId: string | null;

View File

@@ -57,7 +57,7 @@ describe('RoleAssignment', () => {
vi.mocked(fetchRoles).mockResolvedValueOnce([
{
role: 'legacy',
role: 'planner',
providerId: 'p1',
providerName: 'OpenAI',
providerType: 'openai_responses',
@@ -77,7 +77,6 @@ describe('RoleAssignment', () => {
renderWithQuery(<RoleAssignment />);
expect(await screen.findByText('角色分配')).toBeInTheDocument();
expect(await screen.findByText('Legacy 审查')).toBeInTheDocument();
expect(await screen.findByText('规划器 Planner')).toBeInTheDocument();
// Radix Select renders placeholder in a span with pointer-events: none.
@@ -89,11 +88,11 @@ describe('RoleAssignment', () => {
const modelInputs = screen.getAllByPlaceholderText('选择或输入模型...') as HTMLInputElement[];
await waitFor(() => {
expect(modelInputs[1].value).toBe('gpt-4o-mini');
expect(modelInputs[0].value).toBe('gpt-4o');
});
await user.clear(modelInputs[1]);
await user.type(modelInputs[1], 'custom-planner-model');
expect(modelInputs[1].value).toBe('custom-planner-model');
await user.clear(modelInputs[0]);
await user.type(modelInputs[0], 'custom-planner-model');
expect(modelInputs[0].value).toBe('custom-planner-model');
});
});