From b2ae69e3b8402b5d4d3bb2425487437f90ebe7f9 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 29 Mar 2026 07:07:52 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20pipeline=20=E2=80=94=20case-insensitive?= =?UTF-8?q?=20agent=20join,=20remove=20stale=20model=20routing,=20single-t?= =?UTF-8?q?ask=20dispatch,=20300s=20timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JOIN agents uses lower() for case-insensitive name matching - Removed hardcoded 9router/cc/ model IDs — agents use their own OC config - Dispatch LIMIT 3 → 1 to prevent concurrent gateway saturation - Gateway timeout 120s → 300s (engineer agent has 130k token context) - Direct API classifyDirectModel simplified — all routing via OC gateway --- src/lib/task-dispatch.ts | 67 +++++++--------------------------------- 1 file changed, 11 insertions(+), 56 deletions(-) diff --git a/src/lib/task-dispatch.ts b/src/lib/task-dispatch.ts index 751407f..33114b4 100644 --- a/src/lib/task-dispatch.ts +++ b/src/lib/task-dispatch.ts @@ -49,31 +49,8 @@ function classifyTaskModel(task: DispatchableTask): string | null { const text = `${task.title} ${task.description ?? ''}`.toLowerCase() const priority = task.priority?.toLowerCase() ?? '' - // Complex signals → Opus - const complexSignals = [ - 'debug', 'diagnos', 'architect', 'design system', 'security audit', - 'root cause', 'investigate', 'incident', 'failure', 'broken', 'not working', - 'refactor', 'migration', 'performance optim', 'why is', - ] - if (priority === 'critical' || complexSignals.some(s => text.includes(s))) { - return '9router/cc/claude-opus-4-6' - } - - // Routine signals → Haiku - const routineSignals = [ - 'status check', 'health check', 'ping', 'list ', 'fetch ', 'format', - 'rename', 'move file', 'read file', 'update readme', 'bump version', - 'send message', 'post to', 'notify', 'summarize', 'translate', - 'quick ', 'simple ', 'routine ', 'minor ', - ] - if (priority === 'low' && routineSignals.some(s => text.includes(s))) { - return '9router/cc/claude-haiku-4-5-20251001' - } - if (routineSignals.some(s => text.includes(s)) && priority !== 'high' && priority !== 'critical') { - return '9router/cc/claude-haiku-4-5-20251001' - } - - // Default: let the agent's own configured model handle it (no override) + // Let the agent's own configured model handle it — no dispatch-side model override. + // The OC gateway agents already have their models set in openclaw.json. return null } @@ -213,34 +190,12 @@ function classifyDirectModel(task: DispatchableTask): string { try { const cfg = JSON.parse(task.agent_config) if (typeof cfg.dispatchModel === 'string' && cfg.dispatchModel) { - // Return model ID as-is — full provider/model paths are valid return cfg.dispatchModel } } catch { /* ignore */ } } - const text = `${task.title} ${task.description ?? ''}`.toLowerCase() - const priority = task.priority?.toLowerCase() ?? '' - - // Complex → Opus - const complexSignals = [ - 'debug', 'diagnos', 'architect', 'design system', 'security audit', - 'root cause', 'investigate', 'incident', 'refactor', 'migration', - ] - if (priority === 'critical' || complexSignals.some(s => text.includes(s))) { - return 'claude-opus-4-6' - } - - // Routine → Haiku - const routineSignals = [ - 'status check', 'health check', 'format', 'rename', 'summarize', - 'translate', 'quick ', 'simple ', 'routine ', 'minor ', - ] - if (routineSignals.some(s => text.includes(s)) && priority !== 'high' && priority !== 'critical') { - return 'claude-haiku-4-5-20251001' - } - - // Default → Sonnet + // Default → Sonnet (agents configure their own models via OC gateway) return 'claude-sonnet-4-6' } @@ -441,7 +396,7 @@ export async function runAegisReviews(): Promise<{ ok: boolean; message: string p.ticket_prefix, t.project_ticket_no, a.config as agent_config FROM tasks t LEFT JOIN projects p ON p.id = t.project_id AND p.workspace_id = t.workspace_id - LEFT JOIN agents a ON a.name = t.assigned_to AND a.workspace_id = t.workspace_id + LEFT JOIN agents a ON lower(a.name) = lower(t.assigned_to) AND a.workspace_id = t.workspace_id WHERE t.status = 'review' ORDER BY t.updated_at ASC LIMIT 3 @@ -489,8 +444,8 @@ export async function runAegisReviews(): Promise<{ ok: boolean; message: string deliver: false, } const finalResult = await runOpenClaw( - ['gateway', 'call', 'agent', '--expect-final', '--timeout', '120000', '--params', JSON.stringify(invokeParams), '--json'], - { timeoutMs: 125_000 } + ['gateway', 'call', 'agent', '--expect-final', '--timeout', '300000', '--params', JSON.stringify(invokeParams), '--json'], + { timeoutMs: 305_000 } ) const finalPayload = parseGatewayJson(finalResult.stdout) ?? parseGatewayJson(String((finalResult as any)?.stderr || '')) @@ -732,7 +687,7 @@ export async function requeueStaleTasks(): Promise<{ ok: boolean; message: strin SELECT t.id, t.title, t.assigned_to, t.dispatch_attempts, t.workspace_id, a.status as agent_status, a.last_seen as agent_last_seen FROM tasks t - LEFT JOIN agents a ON a.name = t.assigned_to AND a.workspace_id = t.workspace_id + LEFT JOIN agents a ON lower(a.name) = lower(t.assigned_to) AND a.workspace_id = t.workspace_id WHERE t.status = 'in_progress' AND t.updated_at < ? `).all(staleThreshold) as Array<{ @@ -808,14 +763,14 @@ export async function dispatchAssignedTasks(): Promise<{ ok: boolean; message: s SELECT t.*, a.name as agent_name, a.id as agent_id, a.config as agent_config, p.ticket_prefix, t.project_ticket_no FROM tasks t - JOIN agents a ON a.name = t.assigned_to AND a.workspace_id = t.workspace_id + JOIN agents a ON lower(a.name) = lower(t.assigned_to) AND a.workspace_id = t.workspace_id LEFT JOIN projects p ON p.id = t.project_id AND p.workspace_id = t.workspace_id WHERE t.status = 'assigned' AND t.assigned_to IS NOT NULL ORDER BY CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END ASC, t.created_at ASC - LIMIT 3 + LIMIT 1 `).all() as (DispatchableTask & { tags?: string })[] if (tasks.length === 0) { @@ -921,8 +876,8 @@ export async function dispatchAssignedTasks(): Promise<{ ok: boolean; message: s // response payload (result.payloads[0].text). The two-step agent → agent.wait // pattern only returns lifecycle metadata and never includes the agent's text. const finalResult = await runOpenClaw( - ['gateway', 'call', 'agent', '--expect-final', '--timeout', '120000', '--params', JSON.stringify(invokeParams), '--json'], - { timeoutMs: 125_000 } + ['gateway', 'call', 'agent', '--expect-final', '--timeout', '300000', '--params', JSON.stringify(invokeParams), '--json'], + { timeoutMs: 305_000 } ) const finalPayload = parseGatewayJson(finalResult.stdout) ?? parseGatewayJson(String((finalResult as any)?.stderr || ''))