/**
 * CLI Agent Commands
 *
 * Commands for managing Clawx agents.
 *
 * Usage:
 *   clawx agent serve [options]    # Start as headless agent
 *   clawx agent list               # List registered agents (master only)
 *   clawx agent spawn [options]    # Spawn new agent (master only)
 */
import { Command } from 'commander';
import { log } from '../utils/logger.js';
import { setLogLevel } from '../utils/logger.js';
import { AgentRegistryManager } from '../core/agent-registry.js';
import { startAgentServer } from '../core/agent-server.js';
import { v4 as uuidv4 } from 'uuid';
import { join } from 'path';
import { homedir } from 'os';
import { existsSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, readdirSync, rmSync } from 'fs';
export function createAgentCommand() {
    const agentCmd = new Command('agent')
        .description(`Manage Clawx agents (CLI subcommands)

  Additional agent tools are available inside TUI/REPL sessions:
    agent_run            Smart dispatch — auto-pick best worker for a task
    agent_batch          Fan-out — run same tool on all workers/peers in parallel
    agent_network        Unified view of master, workers, peers, and tasks
    agent_remove         One-command agent removal (kill, delete, unregister)
    agent_send           Send a task to a specific agent
    agent_chat           Chat with an agent
    agent_status         Check task status
    agent_result         Get task result
    agent_cleanup        Clean up stale agents
    agent_master_status  Full master and network status
    agent_rehydrate_workers  Restart workers from previous session`)
        .configureHelp({ helpWidth: 80 });
    // agent serve - Start as headless agent
    agentCmd
        .command('serve')
        .description('Start as a headless agent')
        .option('--id <id>', 'Agent ID (default: auto-generated)')
        .option('--name <name>', 'Agent name (default: "agent-<id>")')
        .option('--port <port>', 'Port to listen on (default: 0 = auto)', '0')
        .option('--master <url>', 'Master HTTP endpoint for registration (legacy)')
        .option('--grpc-master <url>', 'Master gRPC endpoint for live communication (grpc://host:port)')
        .option('--workspace <path>', 'Workspace directory')
        .option('--master-workspace <path>', 'Master workspace directory to mirror for worker task context')
        .option('--allowed-tools <tools>', 'Comma-separated allowed tools for this worker')
        .option('--no-auto-rehydrate', 'Disable automatic worker rehydration on startup')
        .option('--foreground', 'Run in foreground instead of daemonizing (for debugging)')
        .option('-v, --verbose', 'Verbose logging')
        .action(async (options) => {
        try {
            if (options.foreground) {
                await serveAgent(options);
            }
            else {
                await daemonizeServe(options);
            }
        }
        catch (error) {
            log.error('Failed to start agent:', error);
            process.exit(1);
        }
    });
    // agent list - List registered agents
    agentCmd
        .command('list')
        .description('List registered agents')
        .action(() => {
        try {
            listAgents();
        }
        catch (error) {
            log.error('Failed to list agents:', error);
            process.exit(1);
        }
    });
    // agent spawn - Spawn new agent
    agentCmd
        .command('spawn')
        .description('Spawn a new agent')
        .option('--name <name>', 'Agent name (required)', '')
        .option('--local', 'Spawn locally (default)')
        .option('--remote <host>', 'Spawn on remote host via SSH')
        .action(async (options) => {
        try {
            await spawnAgent(options);
        }
        catch (error) {
            log.error('Failed to spawn agent:', error);
            process.exit(1);
        }
    });
    // agent cleanup - Clean up stale agents and workspaces
    agentCmd
        .command('cleanup')
        .description('Clean up stale agents, registry entries, and workspace directories')
        .option('--force', 'Force cleanup without confirmation')
        .option('--purge-workspaces', 'Also delete workspace directories for dead agents')
        .option('--threshold <minutes>', 'Stale threshold in minutes (default: 5)', '5')
        .action(async (options) => {
        try {
            await cleanupAgents(options);
        }
        catch (error) {
            log.error('Failed to clean up agents:', error);
            process.exit(1);
        }
    });
    return agentCmd;
}
/** Fork agent serve as a detached daemon — safe for SSH sessions */
async function daemonizeServe(options) {
    const { spawn } = await import('child_process');
    // Child must run in foreground — otherwise it would daemonize again (infinite fork loop)
    const args = ['agent', 'serve', '--foreground'];
    if (options.id)
        args.push('--id', options.id);
    if (options.name)
        args.push('--name', options.name);
    if (options.port && options.port !== '0')
        args.push('--port', options.port);
    if (options.master)
        args.push('--master', options.master);
    if (options.grpcMaster)
        args.push('--grpc-master', options.grpcMaster);
    if (options.workspace)
        args.push('--workspace', options.workspace);
    if (options.masterWorkspace)
        args.push('--master-workspace', options.masterWorkspace);
    if (options.allowedTools)
        args.push('--allowed-tools', options.allowedTools);
    if (options.autoRehydrate === false)
        args.push('--no-auto-rehydrate');
    if (options.verbose)
        args.push('--verbose');
    const name = options.name || 'agent';
    const logDir = join(homedir(), '.clawx', 'logs');
    if (!existsSync(logDir))
        mkdirSync(logDir, { recursive: true });
    const logFile = join(logDir, `${name}.log`);
    // Open log file for stdout/stderr
    const { openSync } = await import('fs');
    const out = openSync(logFile, 'a');
    const err = openSync(logFile, 'a');
    const cmd = process.argv[0]; // node
    const script = process.argv[1]; // clawx entry point
    const child = spawn(cmd, [script, ...args], {
        detached: true,
        stdio: ['ignore', out, err],
        env: { ...process.env },
    });
    child.unref();
    const pidFile = join(logDir, `${name}.pid`);
    writeFileSync(pidFile, String(child.pid), 'utf8');
    console.log(`✅ Agent "${name}" daemonized (PID: ${child.pid})`);
    console.log(`   Log: ${logFile}`);
    console.log(`   PID file: ${pidFile}`);
    process.exit(0);
}
/** Start as headless agent */
async function serveAgent(options) {
    if (options.verbose) {
        setLogLevel('info');
    }
    const agentId = options.id || uuidv4();
    const agentName = options.name || `agent-${agentId.substring(0, 8)}`;
    const port = parseInt(options.port, 10) || 0;
    // Default workspace
    const workspace = options.workspace || join(homedir(), '.clawx', 'agents', agentId);
    if (!existsSync(workspace)) {
        mkdirSync(workspace, { recursive: true });
    }
    log.info(`Starting agent: ${agentName} (${agentId})`);
    log.info(`Workspace: ${workspace}`);
    log.info(`Port: ${port === 0 ? 'auto' : port}`);
    // Write agent config - masterEndpoint will be updated after server starts
    const existingConfigPath = join(workspace, 'agent-config.json');
    let persistedConfig = {};
    try {
        if (existsSync(existingConfigPath)) {
            persistedConfig = JSON.parse(readFileSync(existingConfigPath, 'utf8'));
        }
    }
    catch {
        persistedConfig = {};
    }
    const config = {
        id: agentId,
        name: agentName,
        port,
        workspace,
        masterWorkspace: options.masterWorkspace || persistedConfig.masterWorkspace || '',
        masterEndpoint: options.master || persistedConfig.masterEndpoint || '', // Will be set after server starts
        allowedTools: options.allowedTools ? options.allowedTools.split(',') : (persistedConfig.allowedTools || []),
        ownerMasterId: persistedConfig.ownerMasterId,
        ownerMasterName: persistedConfig.ownerMasterName,
        ownerMasterEndpoint: persistedConfig.ownerMasterEndpoint,
        autoStart: persistedConfig.autoStart,
    };
    const configPath = join(workspace, 'agent-config.json');
    writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
    log.debug(`Config written: ${configPath}`);
    // Start agent server
    const server = await startAgentServer(config);
    log.info(`Agent server started on port ${server.port}`);
    const isMasterLike = !options.master && !options.grpcMaster;
    if (isMasterLike && options.autoRehydrate !== false) {
        setTimeout(async () => {
            try {
                const { agentRehydrateWorkersTool } = await import('../tools/agentRehydrateWorkers.js');
                const rehydrateResult = await agentRehydrateWorkersTool.execute(`auto-rehydrate-${Date.now()}`, { spawn_lock_timeout_ms: 1000 }, undefined, undefined, {
                    __activeMasterConfig: config,
                    masterEndpoint: `http://localhost:${server.port}`,
                });
                const details = rehydrateResult?.details || {};
                log.info(`[auto-rehydrate] matched=${details.matched ?? 0} alive=${Array.isArray(details.alive) ? details.alive.length : 0} restored=${Array.isArray(details.restored) ? details.restored.length : 0} failed=${Array.isArray(details.failed) ? details.failed.length : 0}`);
            }
            catch (error) {
                log.warn(`[auto-rehydrate] failed: ${error instanceof Error ? error.message : String(error)}`);
            }
        }, 1000);
    }
    const logPath = join(workspace, 'peer-activity.log');
    try {
        appendFileSync(logPath, `[${new Date().toISOString()}] agent_started name=${agentName} id=${agentId} port=${server.port}\n`, 'utf8');
    }
    catch { }
    // Update config with actual endpoint
    const actualEndpoint = `http://localhost:${server.port}`;
    config.masterEndpoint = options.master || actualEndpoint;
    // Save updated config
    writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
    // If gRPC master endpoint provided, connect via gRPC instead of HTTP
    if (options.grpcMaster) {
        log.info(`Connecting to master via gRPC: ${options.grpcMaster}`);
        // Import dynamically to avoid circular dependencies
        const { WorkerAgent } = await import('../core/worker-agent.js');
        const workerAgent = new WorkerAgent({
            agentId,
            agentName,
            workspace,
            masterGrpcEndpoint: options.grpcMaster,
            allowedTools: config.allowedTools,
            httpEndpoint: actualEndpoint,
        });
        try {
            await workerAgent.connect();
            log.info(`✅ Worker ${agentName} connected to master via gRPC`);
            // Keep the worker alive
            const keepAlive = () => {
                if (workerAgent.isConnectedToMaster()) {
                    setTimeout(keepAlive, 10000);
                }
            };
            keepAlive();
        }
        catch (error) {
            log.error(`Failed to connect to master via gRPC:`, error);
        }
    }
    else if (options.master && options.master !== actualEndpoint) {
        // Fallback to HTTP registration if no gRPC endpoint
        try {
            const response = await fetch(`${options.master}/register`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    agentId: agentId,
                    agentName: agentName,
                    endpoint: `http://localhost:${server.port}`,
                    capabilities: config.allowedTools,
                }),
            });
            if (response.ok) {
                log.info(`Registered with master via HTTP: ${options.master}`);
            }
            else {
                log.warn(`Failed to register with master: ${response.status}`);
            }
        }
        catch (error) {
            log.warn(`Could not register with master: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    log.info('Press Ctrl+C to stop');
    // Handle shutdown
    process.on('SIGINT', () => {
        log.info('Shutting down agent...');
        server.close();
        process.exit(0);
    });
    process.on('SIGTERM', () => {
        log.info('Terminating agent...');
        server.close();
        process.exit(0);
    });
}
/** List registered agents */
function listAgents() {
    const registry = new AgentRegistryManager();
    const agents = registry.getAgents();
    console.log('\nRegistered Agents:');
    console.log('==================\n');
    if (agents.length === 0) {
        console.log('No agents registered.');
        return;
    }
    for (const agent of agents) {
        console.log(`ID:      ${agent.id}`);
        console.log(`Name:    ${agent.name}`);
        console.log(`Type:    ${agent.type}`);
        console.log(`Status:  ${agent.status}`);
        console.log(`Endpoint: ${agent.endpoint || 'N/A'}`);
        console.log(`Workspace: ${agent.workspace}`);
        console.log(`Created: ${new Date(agent.created).toLocaleString()}`);
        if (agent.lastHeartbeat) {
            const age = Date.now() - agent.lastHeartbeat;
            console.log(`Last heartbeat: ${Math.floor(age / 1000)}s ago`);
        }
        console.log(`Capabilities: ${agent.capabilities.length > 0 ? agent.capabilities.join(', ') : 'all'}`);
        console.log('---');
    }
    console.log(`Total: ${agents.length} agent(s)`);
}
/** Spawn new agent */
async function spawnAgent(options) {
    if (!options.name) {
        log.error('Agent name is required (--name <name>)');
        process.exit(1);
    }
    if (options.remote) {
        log.info(`TODO: Remote spawn to ${options.remote}`);
        log.info('Remote spawn not implemented in v1');
        // TODO: Implement SSH deployment
    }
    else {
        // Local spawn
        await spawnLocalAgent(options.name);
    }
}
/** Spawn local agent */
async function spawnLocalAgent(name) {
    const agentId = uuidv4();
    const registry = new AgentRegistryManager();
    // Check if name already exists
    const existing = registry.getAgentByName(name);
    if (existing) {
        log.error(`Agent with name "${name}" already exists (ID: ${existing.id})`);
        process.exit(1);
    }
    // Create workspace
    const workspace = registry.ensureAgentWorkspace(agentId);
    // Build command to start agent
    const cmd = process.argv[0]; // node
    const script = process.argv[1]; // clawx.js
    const args = [
        'agent', 'serve',
        '--foreground',
        '--id', agentId,
        '--name', name,
        '--workspace', workspace,
    ];
    log.info(`Spawning local agent: ${name} (${agentId})`);
    log.info(`Command: ${cmd} ${script} ${args.join(' ')}`);
    try {
        // Actually spawn the process
        const { spawn } = await import('child_process');
        const agentProcess = spawn(cmd, [script, ...args], {
            cwd: workspace,
            stdio: ['ignore', 'pipe', 'pipe'],
            detached: true,
        });
        // Store process info
        let stdout = '';
        let stderr = '';
        agentProcess.stdout?.on('data', (data) => {
            stdout += data.toString();
            log.debug(`Agent ${name} stdout:`, data.toString().trim());
        });
        agentProcess.stderr?.on('data', (data) => {
            stderr += data.toString();
            log.debug(`Agent ${name} stderr:`, data.toString().trim());
        });
        agentProcess.on('error', (error) => {
            log.error(`Failed to spawn agent ${name}:`, error);
        });
        agentProcess.on('exit', (code, signal) => {
            log.info(`Agent ${name} exited with code ${code}, signal ${signal}`);
        });
        // Unref to allow parent to exit independently
        agentProcess.unref();
        // Register agent in registry
        const agent = {
            id: agentId,
            name,
            type: 'local',
            status: 'starting',
            capabilities: [],
            workspace,
            created: Date.now(),
            pid: agentProcess.pid,
        };
        registry.upsertAgent(agent);
        registry.save();
        console.log(`✅ Agent "${name}" spawned (ID: ${agentId}, PID: ${agentProcess.pid})`);
        console.log(`Workspace: ${workspace}`);
        console.log(`Process detached - will continue running after CLI exits`);
    }
    catch (error) {
        log.error(`Failed to spawn agent ${name}:`, error);
        process.exit(1);
    }
}
/** Clean up stale agents and workspaces */
async function cleanupAgents(options) {
    const { checkAgentHealth, cleanupStaleAgents } = await import('../utils/agent-utils.js');
    const registry = new AgentRegistryManager();
    const agents = registry.getAgents();
    const thresholdMinutes = parseInt(options.threshold, 10) || 5;
    console.log(`\nAgent Cleanup`);
    console.log(`=============\n`);
    console.log(`Found ${agents.length} agents in registry.`);
    if (agents.length === 0) {
        console.log('Nothing to clean up.');
        return;
    }
    // Health check all agents
    const liveIds = new Set();
    let deadCount = 0;
    for (const agent of agents) {
        if (agent.type === 'self') {
            liveIds.add(agent.id);
            continue;
        }
        try {
            const healthy = agent.endpoint ? await checkAgentHealth(agent.endpoint, 3000) : false;
            if (healthy) {
                liveIds.add(agent.id);
            }
            else {
                deadCount++;
                console.log(`  Dead: ${agent.name} (${agent.id})`);
            }
        }
        catch {
            deadCount++;
            console.log(`  Dead: ${agent.name} (${agent.id})`);
        }
    }
    console.log(`\nLive: ${liveIds.size} | Dead: ${deadCount}`);
    // Clean stale from registry
    const cleanedStale = cleanupStaleAgents(thresholdMinutes * 60 * 1000, registry);
    if (cleanedStale > 0) {
        console.log(`Cleaned ${cleanedStale} stale agents from registry.`);
    }
    // Remove dead agents if forced
    if (options.force && deadCount > 0) {
        let removed = 0;
        for (const agent of agents) {
            if (!liveIds.has(agent.id)) {
                registry.removeAgent(agent.id);
                removed++;
            }
        }
        registry.save();
        console.log(`Removed ${removed} dead agents from registry.`);
    }
    // Purge workspace directories
    if (options.purgeWorkspaces) {
        const agentsDir = join(homedir(), '.clawx', 'agents');
        let purged = 0;
        if (existsSync(agentsDir)) {
            for (const entry of readdirSync(agentsDir)) {
                if (liveIds.has(entry))
                    continue;
                try {
                    rmSync(join(agentsDir, entry), { recursive: true, force: true });
                    purged++;
                }
                catch { }
            }
        }
        console.log(`Purged ${purged} stale workspace directories.`);
    }
    console.log('\nDone.');
}
//# sourceMappingURL=agent.js.map