diff --git a/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx b/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx index 0ddff4ea..8b96a5d4 100644 --- a/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx +++ b/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx @@ -622,6 +622,9 @@ export function AgentDefinitionsSection() { const [loading, setLoading] = useState(true); const [batchModel, setBatchModel] = useState(''); const [batchEffort, setBatchEffort] = useState(''); + const [batchType, setBatchType] = useState<'all' | 'codex' | 'claude'>('all'); + const [batchColor, setBatchColor] = useState(''); + const [batchPermission, setBatchPermission] = useState(''); const [batchSaving, setBatchSaving] = useState(false); const loadAgents = useCallback(async () => { @@ -646,28 +649,61 @@ export function AgentDefinitionsSection() { return acc; }, {}); + const batchTargets = agents.filter(a => batchType === 'all' || a.type === batchType); + const batchHasClaudeTargets = batchTargets.some(a => a.type === 'claude'); + const batchEffortOptions = batchType === 'codex' + ? CODEX_EFFORTS + : batchType === 'claude' + ? CLAUDE_EFFORTS + : ['', 'low', 'medium', 'high', 'max (claude only)']; + const handleBatchApply = useCallback(async () => { - if (!batchModel && !batchEffort) { - toast.error('Set a model or effort value first'); + const hasAnyValue = batchModel || batchEffort || batchColor || batchPermission; + if (!hasAnyValue) { + toast.error('Set at least one value first'); + return; + } + if (batchTargets.length === 0) { + toast.error('No agents match the selected type'); return; } setBatchSaving(true); try { - const targets = agents.map(a => ({ filePath: a.filePath, type: a.type })); - const result = await batchUpdateAgentDefinitions({ - targets, - model: batchModel || undefined, - effort: batchEffort || undefined, - }); - toast.success(`Updated ${result.updated}/${result.total} agents`); + // For model/effort, use the batch endpoint + if (batchModel || batchEffort) { + const targets = batchTargets.map(a => ({ filePath: a.filePath, type: a.type })); + const result = await batchUpdateAgentDefinitions({ + targets, + model: batchModel || undefined, + effort: batchEffort || undefined, + }); + toast.success(`Updated model/effort: ${result.updated}/${result.total} agents`); + } + + // For claude-only fields (color, permissionMode), update each claude agent individually + if ((batchColor || batchPermission) && batchHasClaudeTargets) { + const claudeTargets = batchTargets.filter(a => a.type === 'claude'); + let updated = 0; + for (const agent of claudeTargets) { + try { + const body: { filePath: string; [key: string]: string | undefined } = { filePath: agent.filePath }; + if (batchColor) body.color = batchColor; + if (batchPermission) body.permissionMode = batchPermission; + await updateAgentDefinition(agent.type, agent.name, body); + updated++; + } catch { /* skip failed */ } + } + toast.success(`Updated claude fields: ${updated}/${claudeTargets.length} agents`); + } + loadAgents(); } catch (err) { toast.error(`Batch update failed: ${(err as Error).message}`); } finally { setBatchSaving(false); } - }, [agents, batchModel, batchEffort, loadAgents]); + }, [batchTargets, batchHasClaudeTargets, batchModel, batchEffort, batchColor, batchPermission, loadAgents]); return ( @@ -683,40 +719,70 @@ export function AgentDefinitionsSection() { {/* Batch Controls */} -
- Batch: -
- Model: - setBatchModel(e.target.value)} - placeholder="model for all" - /> -
-
- Effort: - setBatchType(e.target.value as 'all' | 'codex' | 'claude')} + > + + + + +
+
+ Model: + setBatchModel(e.target.value)} + placeholder="model" + /> +
+
+ Effort: + +
+ {/* Claude-only batch fields */} + {batchType !== 'codex' && ( + <> +
+ Color: + +
+
+ Permission: + +
+ + )} +
- {/* Agent list */}