diff --git a/.codex/agents/cli-execution-agent.toml b/.codex/agents/cli-execution-agent.toml
index 1aaa9853..84128e5e 100644
--- a/.codex/agents/cli-execution-agent.toml
+++ b/.codex/agents/cli-execution-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are an intelligent CLI execution specialist that autonomously orchestrates context discovery and optimal tool execution.
@@ -331,4 +331,4 @@ Codex unavailable → Gemini/Qwen write mode
- `claude-module-unified.txt` - Universal module/file documentation
---
-"""
+'''
diff --git a/.codex/agents/cli-explore-agent.toml b/.codex/agents/cli-explore-agent.toml
index a6afe95f..17227dbf 100644
--- a/.codex/agents/cli-explore-agent.toml
+++ b/.codex/agents/cli-explore-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
-developer_instructions = """
+developer_instructions = '''
You are a specialized CLI exploration agent that autonomously analyzes codebases and generates structured outputs.
@@ -229,4 +229,4 @@ Brief summary:
**Consumption Pattern**:
- Plan phase: Fully consumes `exploration-notes.md`
- Execute phase: Consumes `exploration-notes-refined.md`, reduced noise, improved efficiency
-"""
+'''
diff --git a/.codex/agents/cli-lite-planning-agent.toml b/.codex/agents/cli-lite-planning-agent.toml
index 4db93934..cb3f838d 100644
--- a/.codex/agents/cli-lite-planning-agent.toml
+++ b/.codex/agents/cli-lite-planning-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are a generic planning agent that generates structured plan JSON for lite workflows. Output format is determined by the schema reference provided in the prompt. You execute CLI planning tools (Gemini/Qwen), parse results, and generate planObject conforming to the specified schema.
@@ -898,4 +898,4 @@ After Phase 4 planObject generation:
5. **Return** → Plan with `_metadata.quality_check` containing execution result
**CLI Fallback**: Gemini → Qwen → Skip with warning (if both fail)
-"""
+'''
diff --git a/.codex/agents/cli-planning-agent.toml b/.codex/agents/cli-planning-agent.toml
index 15fc9b4a..2858130a 100644
--- a/.codex/agents/cli-planning-agent.toml
+++ b/.codex/agents/cli-planning-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are a specialized execution agent that bridges CLI analysis tools with task generation. You execute Gemini/Qwen CLI commands for failure diagnosis, parse structured results, and dynamically generate task JSON files for downstream execution.
@@ -550,4 +550,4 @@ See: `.process/iteration-{iteration}-cli-output.txt`
estimated_complexity: "medium"
}
```
-"""
+'''
diff --git a/.codex/agents/code-developer.toml b/.codex/agents/code-developer.toml
index afc548bd..6747e64a 100644
--- a/.codex/agents/code-developer.toml
+++ b/.codex/agents/code-developer.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are a code execution specialist focused on implementing high-quality, production-ready code. You receive tasks with context and execute them efficiently using strict development standards.
@@ -505,4 +505,4 @@ Before completing any task, verify:
- Document all new interfaces, types, and constants for dependent task reference
### Windows Path Format Guidelines
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
-"""
+'''
diff --git a/.codex/agents/context-search-agent.toml b/.codex/agents/context-search-agent.toml
index b16664bb..30f423ae 100644
--- a/.codex/agents/context-search-agent.toml
+++ b/.codex/agents/context-search-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
-developer_instructions = """
+developer_instructions = '''
You are a context discovery specialist focused on gathering relevant project information for development tasks. Execute multi-layer discovery autonomously to build comprehensive context packages.
@@ -577,4 +577,4 @@ Output: .workflow/session/{session}/.process/context-package.json
### Windows Path Format Guidelines
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
- **Context Package**: Use project-relative paths (e.g., `src/auth/service.ts`)
-"""
+'''
diff --git a/.codex/agents/debug-explore-agent.toml b/.codex/agents/debug-explore-agent.toml
index bd560b64..575d2b96 100644
--- a/.codex/agents/debug-explore-agent.toml
+++ b/.codex/agents/debug-explore-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are an intelligent debugging specialist that autonomously diagnoses bugs through evidence-based hypothesis testing and CLI-assisted analysis.
@@ -434,4 +434,4 @@ ${nextSteps}
- Timeout: Analysis 20min | Fix implementation 40min
---
-"""
+'''
diff --git a/.codex/agents/doc-generator.toml b/.codex/agents/doc-generator.toml
index 8a21bef9..e6301888 100644
--- a/.codex/agents/doc-generator.toml
+++ b/.codex/agents/doc-generator.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are an expert technical documentation specialist. Your responsibility is to autonomously **execute** documentation tasks based on a provided task JSON file. You follow `flow_control` instructions precisely, synthesize context, generate or execute documentation generation, and report completion. You do not make planning decisions.
@@ -322,4 +322,4 @@ Before completing the task, you must verify the following:
- **Generate Code**: Your role is to document, not to implement.
- **Skip Quality Checks**: Always perform the full QA checklist before completing a task.
- **Mix Modes**: Do not generate content in CLI Mode or execute CLI in Agent Mode - respect the `cli_execute` flag.
-"""
+'''
diff --git a/.codex/agents/issue-queue-agent.toml b/.codex/agents/issue-queue-agent.toml
index 5e86dd14..3e8adead 100644
--- a/.codex/agents/issue-queue-agent.toml
+++ b/.codex/agents/issue-queue-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
## Overview
@@ -309,4 +309,4 @@ Return brief summaries; full conflict details in separate files:
```
- `clarifications`: Only present if unresolved high-severity conflicts exist
- No markdown, no prose - PURE JSON only
-"""
+'''
diff --git a/.codex/agents/test-action-planning-agent.toml b/.codex/agents/test-action-planning-agent.toml
index 4e78edda..0bd31908 100644
--- a/.codex/agents/test-action-planning-agent.toml
+++ b/.codex/agents/test-action-planning-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
## Agent Inheritance
@@ -673,4 +673,4 @@ Hard Constraints:
- AI issue detection configured in IMPL-001.3
- Quality gates with measurable thresholds in IMPL-001.5
- Source session status reported (if applicable)
-"""
+'''
diff --git a/.codex/agents/test-fix-agent.toml b/.codex/agents/test-fix-agent.toml
index fb756e46..c4d0fc3e 100644
--- a/.codex/agents/test-fix-agent.toml
+++ b/.codex/agents/test-fix-agent.toml
@@ -4,7 +4,7 @@ model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
-developer_instructions = """
+developer_instructions = '''
You are a specialized **Test Execution & Fix Agent**. Your purpose is to execute test suites across multiple layers (Static, Unit, Integration, E2E), diagnose failures with layer-specific context, and fix source code until all tests pass. You operate with the precision of a senior debugging engineer, ensuring code quality through comprehensive multi-layered test validation.
@@ -354,4 +354,4 @@ jq --arg ts "$(date -Iseconds)" '.status="completed" | .status_history += [{"fro
**Tests passing = Code approved = Mission complete** ✅
### Windows Path Format Guidelines
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
-"""
+'''
diff --git a/11.md b/11.md
new file mode 100644
index 00000000..e69de29b
diff --git a/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx b/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx
new file mode 100644
index 00000000..6ff22624
--- /dev/null
+++ b/ccw/frontend/src/components/settings/AgentDefinitionsSection.tsx
@@ -0,0 +1,305 @@
+// ========================================
+// Agent Definitions Section
+// ========================================
+// Settings section for viewing and editing Codex/Claude agent model and effort fields
+
+import { useState, useEffect, useCallback } from 'react';
+import { Bot, ChevronDown, ChevronRight, Save, RefreshCw } from 'lucide-react';
+import { Card } from '@/components/ui/Card';
+import { Button } from '@/components/ui/Button';
+import { Input } from '@/components/ui/Input';
+import { Badge } from '@/components/ui/Badge';
+import { cn } from '@/lib/utils';
+import { toast } from 'sonner';
+import {
+ fetchAgentDefinitions,
+ updateAgentDefinition,
+ batchUpdateAgentDefinitions,
+ type AgentDefinition,
+} from '@/lib/api';
+
+// ========== Effort options ==========
+
+const CODEX_EFFORTS = ['', 'low', 'medium', 'high'];
+const CLAUDE_EFFORTS = ['', 'low', 'medium', 'high', 'max'];
+const CLAUDE_MODEL_PRESETS = ['sonnet', 'opus', 'haiku', 'inherit'];
+
+// ========== Agent Card ==========
+
+interface AgentCardProps {
+ agent: AgentDefinition;
+ onSaved: () => void;
+}
+
+function AgentCard({ agent, onSaved }: AgentCardProps) {
+ const [model, setModel] = useState(agent.model);
+ const [effort, setEffort] = useState(agent.effort);
+ const [saving, setSaving] = useState(false);
+
+ const isDirty = model !== agent.model || effort !== agent.effort;
+ const effortOptions = agent.type === 'codex' ? CODEX_EFFORTS : CLAUDE_EFFORTS;
+
+ const handleSave = useCallback(async () => {
+ setSaving(true);
+ try {
+ const body: { filePath: string; model?: string; effort?: string } = { filePath: agent.filePath };
+ if (model !== agent.model) body.model = model;
+ if (effort !== agent.effort) body.effort = effort;
+ await updateAgentDefinition(agent.type, agent.name, body);
+ toast.success(`Updated ${agent.name}`);
+ onSaved();
+ } catch (err) {
+ toast.error(`Failed to update ${agent.name}: ${(err as Error).message}`);
+ } finally {
+ setSaving(false);
+ }
+ }, [agent, model, effort, onSaved]);
+
+ // Sync local state when agent prop changes (after refetch)
+ useEffect(() => {
+ setModel(agent.model);
+ setEffort(agent.effort);
+ }, [agent.model, agent.effort]);
+
+ return (
+
+
+ {agent.type}
+
+
+ {agent.name}
+
+
+ {/* Model input */}
+
+
+ {/* Effort select */}
+
+ Effort:
+
+
+
+ {/* Save button */}
+
+
+ );
+}
+
+// ========== Installation Group ==========
+
+interface InstallationGroupProps {
+ installationPath: string;
+ agents: AgentDefinition[];
+ onSaved: () => void;
+}
+
+function InstallationGroup({ installationPath, agents, onSaved }: InstallationGroupProps) {
+ const [expanded, setExpanded] = useState(true);
+
+ return (
+
+
+ {expanded && (
+
+ {agents.map((agent) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+// ========== Main Component ==========
+
+export function AgentDefinitionsSection() {
+ const [agents, setAgents] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [batchModel, setBatchModel] = useState('');
+ const [batchEffort, setBatchEffort] = useState('');
+ const [batchSaving, setBatchSaving] = useState(false);
+
+ const loadAgents = useCallback(async () => {
+ try {
+ setLoading(true);
+ const data = await fetchAgentDefinitions();
+ setAgents(data.agents);
+ } catch (err) {
+ toast.error(`Failed to load agents: ${(err as Error).message}`);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => { loadAgents(); }, [loadAgents]);
+
+ // Group agents by installation path
+ const grouped = agents.reduce>((acc, agent) => {
+ const key = agent.installationPath;
+ if (!acc[key]) acc[key] = [];
+ acc[key].push(agent);
+ return acc;
+ }, {});
+
+ const handleBatchApply = useCallback(async () => {
+ if (!batchModel && !batchEffort) {
+ toast.error('Set a model or effort value first');
+ 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`);
+ loadAgents();
+ } catch (err) {
+ toast.error(`Batch update failed: ${(err as Error).message}`);
+ } finally {
+ setBatchSaving(false);
+ }
+ }, [agents, batchModel, batchEffort, loadAgents]);
+
+ return (
+
+
+
+
+ Agent Definitions
+
+
+
+
+ {/* Batch Controls */}
+
+
Batch:
+
+ Model:
+ setBatchModel(e.target.value)}
+ placeholder="model for all"
+ />
+
+
+ Effort:
+
+
+
+
+
+ {/* Agent list */}
+ {loading ? (
+ Loading agents...
+ ) : agents.length === 0 ? (
+
+ No agent definitions found. Install CCW to a project first.
+
+ ) : (
+
+ {Object.entries(grouped).map(([path, groupAgents]) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+export default AgentDefinitionsSection;
diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts
index 95596080..28125e51 100644
--- a/ccw/frontend/src/lib/api.ts
+++ b/ccw/frontend/src/lib/api.ts
@@ -6203,6 +6203,42 @@ export async function importSettings(
});
}
+// ========== Agent Definitions API ==========
+
+export interface AgentDefinition {
+ name: string;
+ type: 'codex' | 'claude';
+ filePath: string;
+ installationPath: string;
+ model: string;
+ effort: string;
+ description: string;
+}
+
+export async function fetchAgentDefinitions(): Promise<{ agents: AgentDefinition[] }> {
+ return fetchApi('/api/agent-definitions');
+}
+
+export async function updateAgentDefinition(
+ type: 'codex' | 'claude',
+ name: string,
+ body: { filePath: string; model?: string; effort?: string }
+): Promise<{ success: boolean; name: string; type: string; model?: string; effort?: string }> {
+ return fetchApi(`/api/agent-definitions/${type}/${encodeURIComponent(name)}`, {
+ method: 'PUT',
+ body: JSON.stringify(body),
+ });
+}
+
+export async function batchUpdateAgentDefinitions(
+ body: { targets: Array<{ filePath: string; type: 'codex' | 'claude' }>; model?: string; effort?: string }
+): Promise<{ success: boolean; updated: number; total: number; results: Array<{ filePath: string; success: boolean; error?: string }> }> {
+ return fetchApi('/api/agent-definitions/batch', {
+ method: 'PUT',
+ body: JSON.stringify(body),
+ });
+}
+
// ========== CCW Tools API ==========
/**
diff --git a/ccw/frontend/src/pages/SettingsPage.tsx b/ccw/frontend/src/pages/SettingsPage.tsx
index 3c8261be..f184aff0 100644
--- a/ccw/frontend/src/pages/SettingsPage.tsx
+++ b/ccw/frontend/src/pages/SettingsPage.tsx
@@ -62,6 +62,7 @@ import {
import type { ExportedSettings } from '@/lib/api';
import { RemoteNotificationSection } from '@/components/settings/RemoteNotificationSection';
import { A2UIPreferencesSection } from '@/components/settings/A2UIPreferencesSection';
+import { AgentDefinitionsSection } from '@/components/settings/AgentDefinitionsSection';
// ========== CSRF Token Helper ==========
function getCsrfToken(): string | null {
@@ -1478,6 +1479,9 @@ export function SettingsPage() {
/>
+ {/* Agent Definitions */}
+
+
{/* Data Refresh Settings */}
diff --git a/ccw/src/core/routes/agent-definitions-routes.ts b/ccw/src/core/routes/agent-definitions-routes.ts
new file mode 100644
index 00000000..ab0a438c
--- /dev/null
+++ b/ccw/src/core/routes/agent-definitions-routes.ts
@@ -0,0 +1,315 @@
+/**
+ * Agent Definitions Routes Module
+ * Handles discovery, viewing, and editing of Codex (.toml) and Claude (.md) agent definitions
+ */
+
+import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
+import { join, basename } from 'path';
+
+import type { RouteContext } from './types.js';
+import { getAllManifests } from '../manifest.js';
+
+// ========== Types ==========
+
+interface AgentDefinition {
+ name: string;
+ type: 'codex' | 'claude';
+ filePath: string;
+ installationPath: string;
+ model: string;
+ effort: string;
+ description: string;
+}
+
+// ========== Parsing helpers ==========
+
+function parseCodexToml(content: string, filePath: string, installationPath: string): AgentDefinition | null {
+ const nameMatch = content.match(/^name\s*=\s*"([^"]+)"/m);
+ const modelMatch = content.match(/^model\s*=\s*"([^"]+)"/m);
+ const effortMatch = content.match(/^model_reasoning_effort\s*=\s*"([^"]+)"/m);
+ const descMatch = content.match(/^description\s*=\s*"([^"]+)"/m);
+
+ if (!nameMatch) return null;
+
+ return {
+ name: nameMatch[1],
+ type: 'codex',
+ filePath,
+ installationPath,
+ model: modelMatch?.[1] ?? '',
+ effort: effortMatch?.[1] ?? '',
+ description: descMatch?.[1] ?? '',
+ };
+}
+
+function parseClaudeMd(content: string, filePath: string, installationPath: string): AgentDefinition | null {
+ // Extract YAML frontmatter between --- delimiters
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
+ if (!fmMatch) return null;
+
+ const fm = fmMatch[1];
+ const nameMatch = fm.match(/^name:\s*(.+)$/m);
+ const modelMatch = fm.match(/^model:\s*(.+)$/m);
+ const effortMatch = fm.match(/^effort:\s*(.+)$/m);
+ // description can be multi-line with |, just grab first line
+ const descMatch = fm.match(/^description:\s*\|?\s*\n?\s*(.+)$/m);
+
+ if (!nameMatch) return null;
+
+ return {
+ name: nameMatch[1].trim(),
+ type: 'claude',
+ filePath,
+ installationPath,
+ model: modelMatch?.[1].trim() ?? '',
+ effort: effortMatch?.[1].trim() ?? '',
+ description: descMatch?.[1].trim() ?? '',
+ };
+}
+
+// ========== Discovery ==========
+
+function scanAgentsInPath(instPath: string, agents: AgentDefinition[]): void {
+ // Scan .codex/agents/*.toml
+ const codexDir = join(instPath, '.codex', 'agents');
+ if (existsSync(codexDir)) {
+ try {
+ const files = readdirSync(codexDir).filter(f => f.endsWith('.toml'));
+ for (const file of files) {
+ const filePath = join(codexDir, file);
+ try {
+ const content = readFileSync(filePath, 'utf-8');
+ const agent = parseCodexToml(content, filePath, instPath);
+ if (agent) agents.push(agent);
+ } catch { /* skip unreadable */ }
+ }
+ } catch { /* skip unreadable dir */ }
+ }
+
+ // Scan .claude/agents/*.md
+ const claudeDir = join(instPath, '.claude', 'agents');
+ if (existsSync(claudeDir)) {
+ try {
+ const files = readdirSync(claudeDir).filter(f => f.endsWith('.md'));
+ for (const file of files) {
+ const filePath = join(claudeDir, file);
+ try {
+ const content = readFileSync(filePath, 'utf-8');
+ const agent = parseClaudeMd(content, filePath, instPath);
+ if (agent) agents.push(agent);
+ } catch { /* skip unreadable */ }
+ }
+ } catch { /* skip unreadable dir */ }
+ }
+}
+
+function discoverAgents(initialPath: string): AgentDefinition[] {
+ const manifests = getAllManifests();
+ const agents: AgentDefinition[] = [];
+ const scannedPaths = new Set();
+
+ // Scan manifest installation paths
+ for (const manifest of manifests) {
+ const normalized = manifest.installation_path.toLowerCase().replace(/[\\/]+$/, '');
+ if (!scannedPaths.has(normalized)) {
+ scannedPaths.add(normalized);
+ scanAgentsInPath(manifest.installation_path, agents);
+ }
+ }
+
+ // Also scan initialPath (server CWD / project root) if not already covered
+ const normalizedInitial = initialPath.toLowerCase().replace(/[\\/]+$/, '');
+ if (!scannedPaths.has(normalizedInitial)) {
+ scannedPaths.add(normalizedInitial);
+ scanAgentsInPath(initialPath, agents);
+ }
+
+ return agents;
+}
+
+// ========== File update helpers (surgical regex) ==========
+
+function updateCodexTomlField(content: string, field: string, value: string): string {
+ const regex = new RegExp(`^${field}\\s*=\\s*"[^"]*"`, 'm');
+ if (regex.test(content)) {
+ return content.replace(regex, `${field} = "${value}"`);
+ }
+ // Insert after description line if exists, otherwise after first line
+ const descRegex = /^description\s*=\s*"[^"]*"/m;
+ if (descRegex.test(content)) {
+ return content.replace(descRegex, (match) => `${match}\n${field} = "${value}"`);
+ }
+ // Fallback: append after first non-empty line
+ const lines = content.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].trim().length > 0) {
+ lines.splice(i + 1, 0, `${field} = "${value}"`);
+ break;
+ }
+ }
+ return lines.join('\n');
+}
+
+function updateClaudeMdField(content: string, field: string, value: string): string {
+ const fmMatch = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---)/);
+ if (!fmMatch) return content;
+
+ let fm = fmMatch[2];
+ const fieldRegex = new RegExp(`^${field}:\\s*.*$`, 'm');
+
+ if (fieldRegex.test(fm)) {
+ fm = fm.replace(fieldRegex, `${field}: ${value}`);
+ } else {
+ // Append before end of frontmatter
+ fm = fm.trimEnd() + `\n${field}: ${value}`;
+ }
+
+ return fmMatch[1] + fm + fmMatch[3] + content.slice(fmMatch[0].length);
+}
+
+// ========== Validation ==========
+
+const CODEX_EFFORTS = ['low', 'medium', 'high'];
+const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'max'];
+const CLAUDE_MODEL_SHORTCUTS = ['sonnet', 'opus', 'haiku', 'inherit'];
+
+function validateEffort(type: 'codex' | 'claude', effort: string): boolean {
+ if (!effort) return true; // empty = no change
+ return type === 'codex' ? CODEX_EFFORTS.includes(effort) : CLAUDE_EFFORTS.includes(effort);
+}
+
+function validateModel(type: 'codex' | 'claude', model: string): boolean {
+ if (!model) return true; // empty = no change
+ if (type === 'claude') {
+ // Allow shortcuts or full model IDs (any non-empty string)
+ return model.length > 0;
+ }
+ // Codex: any non-empty string
+ return model.length > 0;
+}
+
+// ========== Route handler ==========
+
+export async function handleAgentDefinitionsRoutes(ctx: RouteContext): Promise {
+ const { pathname, req, res, handlePostRequest, initialPath } = ctx;
+
+ // ========== GET /api/agent-definitions ==========
+ if (pathname === '/api/agent-definitions' && req.method === 'GET') {
+ try {
+ const agents = discoverAgents(initialPath);
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify({ agents }));
+ } catch (err) {
+ res.writeHead(500, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify({ error: (err as Error).message }));
+ }
+ return true;
+ }
+
+ // ========== PUT /api/agent-definitions/batch ==========
+ if (pathname === '/api/agent-definitions/batch' && req.method === 'PUT') {
+ handlePostRequest(req, res, async (body: unknown) => {
+ try {
+ const { targets, model, effort } = body as {
+ targets: Array<{ filePath: string; type: 'codex' | 'claude' }>;
+ model?: string;
+ effort?: string;
+ };
+
+ if (!targets || !Array.isArray(targets) || targets.length === 0) {
+ return { error: 'targets array is required', status: 400 };
+ }
+
+ const results: Array<{ filePath: string; success: boolean; error?: string }> = [];
+
+ for (const target of targets) {
+ try {
+ if (!existsSync(target.filePath)) {
+ results.push({ filePath: target.filePath, success: false, error: 'File not found' });
+ continue;
+ }
+
+ if (effort && !validateEffort(target.type, effort)) {
+ results.push({ filePath: target.filePath, success: false, error: `Invalid effort: ${effort}` });
+ continue;
+ }
+ if (model && !validateModel(target.type, model)) {
+ results.push({ filePath: target.filePath, success: false, error: `Invalid model: ${model}` });
+ continue;
+ }
+
+ let content = readFileSync(target.filePath, 'utf-8');
+
+ if (target.type === 'codex') {
+ if (model) content = updateCodexTomlField(content, 'model', model);
+ if (effort) content = updateCodexTomlField(content, 'model_reasoning_effort', effort);
+ } else {
+ if (model) content = updateClaudeMdField(content, 'model', model);
+ if (effort) content = updateClaudeMdField(content, 'effort', effort);
+ }
+
+ writeFileSync(target.filePath, content, 'utf-8');
+ results.push({ filePath: target.filePath, success: true });
+ } catch (err) {
+ results.push({ filePath: target.filePath, success: false, error: (err as Error).message });
+ }
+ }
+
+ const successCount = results.filter(r => r.success).length;
+ return { success: true, updated: successCount, total: targets.length, results };
+ } catch (err) {
+ return { error: (err as Error).message, status: 500 };
+ }
+ });
+ return true;
+ }
+
+ // ========== PUT /api/agent-definitions/:type/:name ==========
+ const putMatch = pathname.match(/^\/api\/agent-definitions\/(codex|claude)\/([^/]+)$/);
+ if (putMatch && req.method === 'PUT') {
+ const agentType = putMatch[1] as 'codex' | 'claude';
+ const agentName = decodeURIComponent(putMatch[2]);
+
+ handlePostRequest(req, res, async (body: unknown) => {
+ try {
+ const { filePath, model, effort } = body as {
+ filePath: string;
+ model?: string;
+ effort?: string;
+ };
+
+ if (!filePath) {
+ return { error: 'filePath is required', status: 400 };
+ }
+ if (!existsSync(filePath)) {
+ return { error: 'File not found', status: 404 };
+ }
+ if (effort && !validateEffort(agentType, effort)) {
+ return { error: `Invalid effort value: ${effort}. Valid: ${agentType === 'codex' ? CODEX_EFFORTS.join(', ') : CLAUDE_EFFORTS.join(', ')}`, status: 400 };
+ }
+ if (model && !validateModel(agentType, model)) {
+ return { error: 'Invalid model value', status: 400 };
+ }
+
+ let content = readFileSync(filePath, 'utf-8');
+
+ if (agentType === 'codex') {
+ if (model) content = updateCodexTomlField(content, 'model', model);
+ if (effort) content = updateCodexTomlField(content, 'model_reasoning_effort', effort);
+ } else {
+ if (model) content = updateClaudeMdField(content, 'model', model);
+ if (effort) content = updateClaudeMdField(content, 'effort', effort);
+ }
+
+ writeFileSync(filePath, content, 'utf-8');
+
+ return { success: true, name: agentName, type: agentType, model, effort };
+ } catch (err) {
+ return { error: (err as Error).message, status: 500 };
+ }
+ });
+ return true;
+ }
+
+ return false;
+}
diff --git a/ccw/src/core/server.ts b/ccw/src/core/server.ts
index 35098753..b685e6c1 100644
--- a/ccw/src/core/server.ts
+++ b/ccw/src/core/server.ts
@@ -47,6 +47,7 @@ import { handleAnalysisRoutes } from './routes/analysis-routes.js';
import { handleSpecRoutes } from './routes/spec-routes.js';
import { handleDeepWikiRoutes } from './routes/deepwiki-routes.js';
import { handleCodexLensRoutes } from './routes/codexlens-routes.js';
+import { handleAgentDefinitionsRoutes } from './routes/agent-definitions-routes.js';
// Import WebSocket handling
import { handleWebSocketUpgrade, broadcastToClients, extractSessionIdFromPath } from './websocket.js';
@@ -517,6 +518,11 @@ export async function startServer(options: ServerOptions = {}): Promise