/**
 * Agent Run Tool — Smart Dispatch
 *
 * High-level tool that automatically selects the best available worker
 * and dispatches a task. Replaces manual worker selection with:
 *   agent_run --tool bash --params '{"command": "npm test"}'
 *
 * Selection logic:
 * 1. Prefer idle local workers over busy ones
 * 2. Prefer workers whose capabilities match the requested tool
 * 3. Fall back to remote peer workers if no local workers available
 * 4. Health-check the chosen worker before dispatch
 */
import { log } from '../utils/logger.js';
import { AgentRegistryManager } from '../core/agent-registry.js';
import { checkAgentHealth } from '../utils/agent-utils.js';
import { agentMaster } from '../core/agent-master.js';
import { v4 as uuidv4 } from 'uuid';
import { withGrpcWorkerStreaming } from '../utils/grpc-streaming-tool-helper.js';
function scoreWorker(agent, tool) {
    let score = 0;
    // Status scoring
    if (agent.status === 'idle')
        score += 10;
    else if (agent.status === 'working')
        score += 2;
    // offline/error/starting get 0
    // Capability match
    const caps = agent.capabilities || [];
    if (caps.length === 0 || caps.includes('all')) {
        score += 5; // Can do anything
    }
    else if (caps.includes(tool)) {
        score += 8; // Explicit match
    }
    else {
        score -= 5; // Can't run this tool
    }
    // Prefer local workers (lower latency)
    // (added externally based on location)
    return score;
}
function extractReadableResult(value) {
    if (!value)
        return '';
    if (typeof value === 'string')
        return value;
    if (Array.isArray(value?.content)) {
        return value.content
            .filter((item) => item?.type === 'text' && typeof item.text === 'string')
            .map((item) => item.text)
            .join('\n');
    }
    if (typeof value?.output === 'string')
        return value.output;
    if (value?.result)
        return extractReadableResult(value.result);
    return '';
}
export const agentRunTool = {
    name: 'agent_run',
    label: 'Run Task (Smart Dispatch)',
    description: 'Automatically pick the best available worker and run a tool task. No need to specify which worker — agent_run finds one for you.',
    promptSnippet: 'Use agent_run for automatic worker selection. It finds the best idle worker (local or peer) and dispatches the task. Example: agent_run --tool bash --params {"command": "npm test"}',
    parameters: {
        type: 'object',
        properties: {
            tool: {
                type: 'string',
                description: 'Tool name to execute (e.g. bash, read, write, search_files)',
            },
            params: {
                type: 'object',
                description: 'Tool parameters',
                default: {},
            },
            prefer: {
                type: 'string',
                description: 'Prefer a specific worker by name (still auto-selects if unavailable)',
            },
            local_only: {
                type: 'boolean',
                description: 'Only consider local workers, skip peer workers',
                default: false,
            },
        },
        required: ['tool'],
    },
    async execute(_toolCallId, params, signal, onUpdate, context) {
        const tool = params.tool;
        const taskParams = params.params || {};
        const preferName = params.prefer;
        const localOnly = params.local_only === true;
        try {
            // Must be serving as master to dispatch
            if (!agentMaster.isServing()) {
                return {
                    content: [{ type: 'text', text: 'Not serving as master. Run `agent_serve` first, then `agent_spawn_local` to create workers.' }],
                    isError: true,
                };
            }
            const registry = new AgentRegistryManager();
            registry.markOfflineAgents();
            const agents = registry.getAgents();
            // Build candidate list from local workers
            const candidates = [];
            for (const agent of agents) {
                if (agent.type !== 'local')
                    continue;
                if (agent.status === 'offline' || agent.status === 'error')
                    continue;
                if (!agent.endpoint)
                    continue;
                candidates.push({
                    agent,
                    location: 'local',
                    score: scoreWorker(agent, tool) + 3, // +3 local bonus
                });
            }
            // Query peer workers if allowed
            if (!localOnly) {
                const peers = agents.filter(a => a.type === 'remote' && a.endpoint);
                for (const peer of peers) {
                    try {
                        const controller = new AbortController();
                        const timeoutId = setTimeout(() => controller.abort(), 3000);
                        const res = await fetch(`${peer.endpoint}/agents`, { signal: controller.signal });
                        clearTimeout(timeoutId);
                        if (res.ok) {
                            const peerAgents = await res.json();
                            for (const pa of peerAgents) {
                                if (pa.type !== 'local' || pa.status === 'offline' || pa.status === 'error')
                                    continue;
                                candidates.push({
                                    agent: pa,
                                    location: 'peer',
                                    peerName: peer.name,
                                    peerEndpoint: peer.endpoint,
                                    score: scoreWorker(pa, tool),
                                });
                            }
                        }
                    }
                    catch {
                        // Peer unreachable, skip
                    }
                }
            }
            if (candidates.length === 0) {
                return {
                    content: [{
                            type: 'text',
                            text: 'No available workers found.\n\n' +
                                '- Spawn a local worker: `agent_spawn_local --name worker1`\n' +
                                '- Add a remote peer: `agent_peer_add --name peer --endpoint http://host:port`',
                        }],
                    isError: true,
                };
            }
            // If preferred worker specified, boost its score
            if (preferName) {
                for (const c of candidates) {
                    if (c.agent.name === preferName) {
                        c.score += 100;
                    }
                }
            }
            // Sort by score descending
            candidates.sort((a, b) => b.score - a.score);
            // Health-check top candidates until we find one that's alive
            let chosen = null;
            for (const candidate of candidates.slice(0, 5)) {
                const endpoint = candidate.location === 'local'
                    ? candidate.agent.endpoint
                    : candidate.agent.endpoint;
                const healthy = await checkAgentHealth(endpoint, 3000);
                if (healthy) {
                    chosen = candidate;
                    break;
                }
            }
            if (!chosen) {
                return {
                    content: [{
                            type: 'text',
                            text: `Found ${candidates.length} worker(s) but none responded to health check. Workers may need restarting.`,
                        }],
                    isError: true,
                };
            }
            const chosenAgent = chosen.agent;
            const locationLabel = chosen.location === 'peer'
                ? `${chosen.peerName} -> ${chosenAgent.name}`
                : chosenAgent.name;
            // Dispatch based on location
            if (chosen.location === 'peer') {
                // Dispatch to remote master, which uses gRPC internally to its workers
                const startedAt = Date.now();
                onUpdate?.({
                    content: [{ type: 'text', text: `Dispatching ${tool} to ${locationLabel}` }],
                    details: { agent_name: chosenAgent.name, peer_name: chosen.peerName, tool, stream: true },
                });
                const res = await fetch(`${chosen.peerEndpoint}/task`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'x-clawx-peer-source': 'agent-run',
                    },
                    body: JSON.stringify({
                        tool,
                        params: taskParams,
                        targetAgentId: chosenAgent.id,
                        context: { __transport: 'peer_grpc' },
                    }),
                });
                if (!res.ok) {
                    return {
                        content: [{ type: 'text', text: `Failed to dispatch to ${locationLabel}: ${res.status}` }],
                        isError: true,
                    };
                }
                const accepted = await res.json();
                const taskId = accepted.taskId || accepted.id;
                if (!taskId) {
                    return {
                        content: [{ type: 'text', text: `${locationLabel} accepted but returned no task ID` }],
                        details: { accepted },
                    };
                }
                // Wait for remote gRPC execution to complete
                const { waitForPeerTaskResult } = await import('./agentPeerTaskHelpers.js');
                const { status: finalStatus, result: finalResult } = await waitForPeerTaskResult(chosen.peerEndpoint, taskId, 60000);
                const duration = ((Date.now() - startedAt) / 1000).toFixed(1);
                const readable = extractReadableResult(finalResult).trim();
                if (finalStatus === 'completed') {
                    return {
                        content: [{ type: 'text', text: `${locationLabel} completed ${tool} (${duration}s)${readable ? `\n${readable}` : ''}` }],
                        details: { task_id: taskId, agent_name: chosenAgent.name, peer_name: chosen.peerName, tool, status: finalStatus, result: finalResult },
                    };
                }
                return {
                    content: [{ type: 'text', text: `${locationLabel} ${tool}: ${finalStatus} (${duration}s)${readable ? `\n${readable}` : ''}` }],
                    details: { task_id: taskId, agent_name: chosenAgent.name, peer_name: chosen.peerName, tool, status: finalStatus },
                    isError: finalStatus === 'failed',
                };
            }
            else {
                // Local dispatch via gRPC (same as agent_send)
                const masterServer = agentMaster.getServer();
                const masterConfig = agentMaster.getConfig();
                const grpcServer = masterServer?.grpcServer;
                if (!masterServer?.grpcPort || !masterConfig || !grpcServer?.sendTask) {
                    return {
                        content: [{ type: 'text', text: 'Master gRPC server not available. Ensure agent_serve was run successfully.' }],
                        isError: true,
                    };
                }
                const taskId = uuidv4();
                const sanitizedContext = {
                    cwd: context?.cwd,
                    workerWorkspace: context?.workerWorkspace,
                    masterWorkspace: context?.masterWorkspace,
                    __transport: 'grpc',
                };
                // Register task
                const task = {
                    id: taskId,
                    agentId: chosenAgent.id,
                    type: 'execute',
                    payload: { tool, params: taskParams, context: sanitizedContext },
                    status: 'pending',
                    created: Date.now(),
                };
                registry.addTask(task);
                registry.save();
                let streamedDisplay = '';
                const pushPartial = () => {
                    onUpdate?.({
                        content: [{ type: 'text', text: streamedDisplay }],
                        details: { task_id: taskId, agent_name: chosenAgent.name, tool, stream: true },
                    });
                };
                const prettyOnUpdate = (update) => {
                    switch (update?.type) {
                        case 'task_started':
                            streamedDisplay += `\n${chosenAgent.name} starting ${tool}\n`;
                            pushPartial();
                            break;
                        case 'tool_stdout':
                            streamedDisplay += update.data.endsWith('\n') ? update.data : `${update.data}\n`;
                            pushPartial();
                            break;
                        case 'tool_stderr':
                            streamedDisplay += `[stderr] ${update.data.endsWith('\n') ? update.data : `${update.data}\n`}`;
                            pushPartial();
                            break;
                        case 'task_completed':
                            streamedDisplay += `\n[completed]\n`;
                            pushPartial();
                            break;
                        case 'task_failed':
                            streamedDisplay += `\n[failed] ${update.error || 'Unknown error'}\n`;
                            pushPartial();
                            break;
                    }
                };
                const abortHandler = () => {
                    grpcServer.cancelTask(masterConfig.id, chosenAgent.id, taskId, 'Aborted');
                };
                if (signal)
                    signal.addEventListener('abort', abortHandler, { once: true });
                const streamingResult = await withGrpcWorkerStreaming({
                    agentId: chosenAgent.id,
                    agentName: chosenAgent.name,
                    operationId: taskId,
                    operationType: 'task',
                    onUpdate: prettyOnUpdate,
                    signal,
                }, async () => {
                    const sent = grpcServer.sendTask(masterConfig.id, chosenAgent.id, taskId, tool, taskParams, sanitizedContext);
                    if (!sent)
                        throw new Error(`Failed to send task to ${chosenAgent.name} over gRPC`);
                    registry.updateTask(taskId, { status: 'running', started: Date.now() });
                    registry.save();
                    return { status: 'accepted', transport: 'grpc', taskId };
                });
                const { finalResult: grpcResult, events } = streamingResult;
                if (signal)
                    signal.removeEventListener('abort', abortHandler);
                let finalStatus = 'running';
                let finalTaskResult = null;
                for (const event of events) {
                    if (event.type === 'task_completed') {
                        finalStatus = 'completed';
                        finalTaskResult = event.result;
                        break;
                    }
                    else if (event.type === 'task_failed') {
                        finalStatus = 'failed';
                        finalTaskResult = { error: event.error };
                        break;
                    }
                    else if (event.type === 'task_cancelled') {
                        finalStatus = 'cancelled';
                        break;
                    }
                }
                registry.updateTask(taskId, {
                    status: finalStatus,
                    completed: Date.now(),
                    result: finalTaskResult,
                });
                registry.save();
                const readable = extractReadableResult(finalTaskResult).trim();
                let responseText = '';
                if (finalStatus === 'completed') {
                    responseText = `${chosenAgent.name} completed ${tool}${readable ? `\n${readable}` : ''}`;
                }
                else if (finalStatus === 'failed') {
                    responseText = `${chosenAgent.name} failed ${tool}${finalTaskResult?.error ? `\n${finalTaskResult.error}` : ''}`;
                }
                else if (finalStatus === 'cancelled') {
                    responseText = `${chosenAgent.name} cancelled ${tool}`;
                }
                else {
                    responseText = `${chosenAgent.name} ${tool}: ${finalStatus}`;
                }
                return {
                    content: [{ type: 'text', text: responseText }],
                    details: {
                        task_id: taskId,
                        agent_name: chosenAgent.name,
                        dispatched_to: 'local',
                        tool,
                        status: finalStatus,
                        result: finalTaskResult,
                    },
                    isError: finalStatus === 'failed',
                };
            }
        }
        catch (error) {
            log.error('agent_run failed:', error);
            return {
                content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }],
                isError: true,
            };
        }
    },
};
//# sourceMappingURL=agentRun.js.map