diff --git a/ccw/src/core/routes/loop-v2-routes.ts b/ccw/src/core/routes/loop-v2-routes.ts index 65f36292..a3bd4e1e 100644 --- a/ccw/src/core/routes/loop-v2-routes.ts +++ b/ccw/src/core/routes/loop-v2-routes.ts @@ -29,11 +29,13 @@ import { join } from 'path'; import { randomBytes } from 'crypto'; +import * as os from 'os'; import type { RouteContext } from './types.js'; import { LoopStatus } from '../../types/loop.js'; import type { LoopState } from '../../types/loop.js'; import { TaskStorageManager, type TaskCreateRequest, type TaskUpdateRequest, type TaskReorderRequest } from '../../tools/loop-task-manager.js'; import { executeCliTool } from '../../tools/cli-executor.js'; +import { loadClaudeCliTools } from '../../tools/claude-cli-tools.js'; /** * V2 Loop Create Request @@ -710,9 +712,17 @@ export async function handleLoopV2Routes(ctx: RouteContext): Promise { return { success: false, error: 'tool is required', status: 400 }; } - const validTools = ['bash', 'gemini', 'codex', 'qwen', 'claude']; + // Get enabled tools from cli-tools.json dynamically + const cliToolsConfig = loadClaudeCliTools(os.homedir()); + const enabledTools = Object.entries(cliToolsConfig.tools || {}) + .filter(([_, config]) => config.enabled === true) + .map(([name]) => name); + + // Also allow 'bash' as a special case (built-in tool) + const validTools = ['bash', ...enabledTools]; + if (!validTools.includes(tool)) { - return { success: false, error: `tool must be one of: ${validTools.join(', ')}`, status: 400 }; + return { success: false, error: `tool must be one of enabled tools: ${validTools.join(', ')}`, status: 400 }; } if (!mode || typeof mode !== 'string') { @@ -1303,12 +1313,23 @@ function isValidId(id: string): boolean { return true; } +/** + * Get enabled tools list + */ +function getEnabledToolsList(): string[] { + const cliToolsConfig = loadClaudeCliTools(os.homedir()); + const enabledTools = Object.entries(cliToolsConfig.tools || {}) + .filter(([_, config]) => config.enabled === true) + .map(([name]) => name); + return ['bash', ...enabledTools]; +} + /** * Map issue tool to loop tool */ -function mapIssueToolToLoopTool(tool: any): 'bash' | 'gemini' | 'codex' | 'qwen' | 'claude' | null { - const validTools = ['bash', 'gemini', 'codex', 'qwen', 'claude']; - if (validTools.includes(tool)) return tool as any; +function mapIssueToolToLoopTool(tool: any): string | null { + const validTools = getEnabledToolsList(); + if (validTools.includes(tool)) return tool; // Map aliases if (tool === 'ccw') return 'gemini'; if (tool === 'ai') return 'gemini'; @@ -1343,7 +1364,7 @@ function mapIssueOnError(onError: any): 'continue' | 'pause' | 'fail_fast' | und * Validate tool value */ function validateTool(tool: any): boolean { - const validTools = ['bash', 'gemini', 'codex', 'qwen', 'claude']; + const validTools = getEnabledToolsList(); return validTools.includes(tool); } diff --git a/ccw/src/templates/dashboard-js/i18n.js b/ccw/src/templates/dashboard-js/i18n.js index e29d2096..6e0e26f6 100644 --- a/ccw/src/templates/dashboard-js/i18n.js +++ b/ccw/src/templates/dashboard-js/i18n.js @@ -2202,6 +2202,9 @@ const i18n = { 'loop.kanban.noBoardData': 'No tasks to display', 'loop.listView': 'List View', 'loop.addTask': 'Add Task', + 'loop.add': 'Add', + 'loop.save': 'Save', + 'loop.cancel': 'Cancel', // Navigation & Grouping 'loop.nav.groupBy': 'Group By', @@ -4796,6 +4799,9 @@ const i18n = { 'loop.kanban.noBoardData': '没有要显示的任务', 'loop.listView': '列表视图', 'loop.addTask': '添加任务', + 'loop.add': '添加', + 'loop.save': '保存', + 'loop.cancel': '取消', // Navigation & Grouping 'loop.nav.groupBy': '分组', diff --git a/ccw/src/tools/loop-task-manager.ts b/ccw/src/tools/loop-task-manager.ts index e24b2b18..0468090b 100644 --- a/ccw/src/tools/loop-task-manager.ts +++ b/ccw/src/tools/loop-task-manager.ts @@ -22,8 +22,8 @@ export interface LoopTask { /** Task description (what to do) */ description: string; - /** CLI tool to use */ - tool: 'bash' | 'gemini' | 'codex' | 'qwen' | 'claude'; + /** CLI tool to use (bash, builtin tools, cli-wrapper, api-endpoint) */ + tool: string; /** Execution mode */ mode: 'analysis' | 'write' | 'review';