From d53d93351cbfa0084b679ab49aa829c0a6d4e058 Mon Sep 17 00:00:00 2001 From: Nyk <0xnykcd@googlemail.com> Date: Fri, 13 Mar 2026 12:32:25 +0700 Subject: [PATCH] fix(tasks): use gateway agent ID instead of display name for dispatch Task dispatch and Aegis review were sending `agents.name` (the display name) as `agentId` to the gateway. When `identity.name` differs from the gateway agent `id`, the gateway rejects the call with "unknown agent id". Now extracts `openclawId` from the agent's config JSON (set during agent sync) and uses that for gateway invocations, falling back to the display name for backwards compatibility. Closes #310 --- src/lib/task-dispatch.ts | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib/task-dispatch.ts b/src/lib/task-dispatch.ts index c3e4aa2..e52825b 100644 --- a/src/lib/task-dispatch.ts +++ b/src/lib/task-dispatch.ts @@ -13,12 +13,25 @@ interface DispatchableTask { workspace_id: number agent_name: string agent_id: number + agent_config: string | null ticket_prefix: string | null project_ticket_no: number | null project_id: number | null tags?: string[] } +/** Extract the gateway agent identifier from the agent's config JSON. + * Falls back to agent_name (display name) if openclawId is not set. */ +function resolveGatewayAgentId(task: DispatchableTask): string { + if (task.agent_config) { + try { + const cfg = JSON.parse(task.agent_config) + if (typeof cfg.openclawId === 'string' && cfg.openclawId) return cfg.openclawId + } catch { /* ignore */ } + } + return task.agent_name +} + function buildTaskPrompt(task: DispatchableTask, rejectionFeedback?: string | null): string { const ticket = task.ticket_prefix && task.project_ticket_no ? `${task.ticket_prefix}-${String(task.project_ticket_no).padStart(3, '0')}` @@ -94,11 +107,22 @@ interface ReviewableTask { description: string | null resolution: string | null assigned_to: string | null + agent_config: string | null workspace_id: number ticket_prefix: string | null project_ticket_no: number | null } +function resolveGatewayAgentIdForReview(task: ReviewableTask): string { + if (task.agent_config) { + try { + const cfg = JSON.parse(task.agent_config) + if (typeof cfg.openclawId === 'string' && cfg.openclawId) return cfg.openclawId + } catch { /* ignore */ } + } + return task.assigned_to || 'jarv' +} + function buildReviewPrompt(task: ReviewableTask): string { const ticket = task.ticket_prefix && task.project_ticket_no ? `${task.ticket_prefix}-${String(task.project_ticket_no).padStart(3, '0')}` @@ -154,9 +178,10 @@ export async function runAegisReviews(): Promise<{ ok: boolean; message: string const tasks = db.prepare(` SELECT t.id, t.title, t.description, t.resolution, t.assigned_to, t.workspace_id, - p.ticket_prefix, t.project_ticket_no + 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 WHERE t.status = 'review' ORDER BY t.updated_at ASC LIMIT 3 @@ -181,8 +206,8 @@ export async function runAegisReviews(): Promise<{ ok: boolean; message: string try { const prompt = buildReviewPrompt(task) - // Use the assigned agent or fall back to a default reviewer agent - const reviewAgent = task.assigned_to || 'jarv' + // Resolve the gateway agent ID from config, falling back to assigned_to or default + const reviewAgent = resolveGatewayAgentIdForReview(task) const invokeParams = { message: prompt, @@ -290,7 +315,7 @@ export async function dispatchAssignedTasks(): Promise<{ ok: boolean; message: s const db = getDatabase() const tasks = db.prepare(` - SELECT t.*, a.name as agent_name, a.id as agent_id, + 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 @@ -350,9 +375,10 @@ export async function dispatchAssignedTasks(): Promise<{ ok: boolean; message: s const prompt = buildTaskPrompt(task, rejectionFeedback) // Step 1: Invoke via gateway + const gatewayAgentId = resolveGatewayAgentId(task) const invokeParams = { message: prompt, - agentId: task.agent_name, + agentId: gatewayAgentId, idempotencyKey: `task-dispatch-${task.id}-${Date.now()}`, deliver: false, }