import { randomUUID } from 'crypto';
import { CoordinationDatabase, getDefaultCoordinationDbPath } from './database.js';
import { DeployBatcher } from './deploy-batcher.js';
export class CoordinationService {
    db;
    dbPath;
    sessionId;
    heartbeatIntervalMs;
    claimExpiryMs;
    constructor(config = {}) {
        this.dbPath = config.dbPath || getDefaultCoordinationDbPath();
        this.db = CoordinationDatabase.getInstance(this.dbPath).getDatabase();
        this.sessionId = config.sessionId || randomUUID();
        this.heartbeatIntervalMs = config.heartbeatIntervalMs || 30000;
        this.claimExpiryMs = config.claimExpiryMs || 300000; // 5 minutes
    }
    // ==================== Agent Lifecycle ====================
    /**
     * Register an agent. Pass `id` to register under a stable, caller-chosen
     * identifier so other agents can address it directly (announce/send/heartbeat
     * all require a known id); omit it for an auto-generated UUID. Re-registering
     * an existing id is idempotent — it reactivates the agent and refreshes its
     * metadata rather than failing on the primary-key conflict.
     */
    register(name, capabilities, worktreeBranch, id) {
        const agentId = id || randomUUID();
        const now = new Date().toISOString();
        const stmt = this.db.prepare(`
      INSERT INTO agent_registry (id, name, session_id, status, worktree_branch, started_at, last_heartbeat, capabilities)
      VALUES (?, ?, ?, 'active', ?, ?, ?, ?)
      ON CONFLICT(id) DO UPDATE SET
        name = excluded.name,
        session_id = excluded.session_id,
        status = 'active',
        worktree_branch = excluded.worktree_branch,
        last_heartbeat = excluded.last_heartbeat,
        capabilities = excluded.capabilities
    `);
        stmt.run(agentId, name, this.sessionId, worktreeBranch || null, now, now, capabilities ? JSON.stringify(capabilities) : null);
        return agentId;
    }
    heartbeat(agentId) {
        const stmt = this.db.prepare(`
      UPDATE agent_registry
      SET last_heartbeat = ?
      WHERE id = ?
    `);
        stmt.run(new Date().toISOString(), agentId);
    }
    updateStatus(agentId, status, currentTask) {
        const stmt = this.db.prepare(`
      UPDATE agent_registry
      SET status = ?, current_task = ?, last_heartbeat = ?
      WHERE id = ?
    `);
        stmt.run(status, currentTask || null, new Date().toISOString(), agentId);
    }
    deregister(agentId) {
        // Release all claims
        this.releaseAllClaims(agentId);
        // Update status
        const stmt = this.db.prepare(`
      UPDATE agent_registry
      SET status = 'completed'
      WHERE id = ?
    `);
        stmt.run(agentId);
    }
    getAgent(agentId) {
        const stmt = this.db.prepare(`
      SELECT id, name, session_id as sessionId, status, current_task as currentTask,
             worktree_branch as worktreeBranch, started_at as startedAt, 
             last_heartbeat as lastHeartbeat, capabilities
      FROM agent_registry
      WHERE id = ?
    `);
        const row = stmt.get(agentId);
        if (!row)
            return null;
        return {
            ...row,
            capabilities: row.capabilities ? JSON.parse(row.capabilities) : undefined,
        };
    }
    getActiveAgents() {
        const stmt = this.db.prepare(`
      SELECT id, name, session_id as sessionId, status, current_task as currentTask,
             worktree_branch as worktreeBranch, started_at as startedAt, 
             last_heartbeat as lastHeartbeat, capabilities
      FROM agent_registry
      WHERE status IN ('active', 'idle')
      ORDER BY started_at DESC
    `);
        const rows = stmt.all();
        return rows.map((row) => ({
            ...row,
            capabilities: row.capabilities ? JSON.parse(row.capabilities) : undefined,
        }));
    }
    // Cleanup stale agents (no heartbeat for too long)
    cleanupStaleAgents() {
        const cutoff = new Date(Date.now() - this.heartbeatIntervalMs * 3).toISOString();
        return this.cleanupAgentsOlderThan(cutoff);
    }
    // Cleanup agents with heartbeat older than a specific duration in hours
    cleanupStaleAgentsByTime(hours = 24) {
        const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
        return this.cleanupAgentsOlderThan(cutoff);
    }
    cleanupAgentsOlderThan(cutoff) {
        // Get stale agents
        const staleStmt = this.db.prepare(`
      SELECT id FROM agent_registry
      WHERE status IN ('active', 'idle') AND last_heartbeat < ?
    `);
        const staleAgents = staleStmt.all(cutoff);
        // Release their claims and complete their announcements
        for (const agent of staleAgents) {
            this.releaseAllClaims(agent.id);
            this.db
                .prepare(`
        UPDATE work_announcements SET completed_at = ?
        WHERE agent_id = ? AND completed_at IS NULL
      `)
                .run(new Date().toISOString(), agent.id);
        }
        // Mark as failed
        const updateStmt = this.db.prepare(`
      UPDATE agent_registry
      SET status = 'failed'
      WHERE status IN ('active', 'idle') AND last_heartbeat < ?
    `);
        const result = updateStmt.run(cutoff);
        return result.changes;
    }
    // ==================== Work Claims ====================
    claimResource(agentId, resource, claimType = 'exclusive') {
        this.ensureAgentIsRegistered(agentId);
        const now = new Date().toISOString();
        const expiresAt = new Date(Date.now() + this.claimExpiryMs).toISOString();
        // Wrap check-then-insert in a transaction to prevent race conditions
        // where two agents could simultaneously acquire "exclusive" claims
        const claimTransaction = this.db.transaction(() => {
            // Check for existing active claims on this resource
            const checkStmt = this.db.prepare(`
        SELECT agent_id, claim_type FROM work_claims
        WHERE resource = ? AND (expires_at IS NULL OR expires_at > ?)
      `);
            const existing = checkStmt.get(resource, now);
            if (existing) {
                if (existing.claim_type === 'exclusive') {
                    return false; // Resource already exclusively claimed
                }
                if (claimType === 'exclusive') {
                    return false; // Can't get exclusive claim when shared claims exist
                }
            }
            const stmt = this.db.prepare(`
        INSERT INTO work_claims (resource, agent_id, claim_type, claimed_at, expires_at)
        VALUES (?, ?, ?, ?, ?)
      `);
            stmt.run(resource, agentId, claimType, now, expiresAt);
            return true;
        });
        try {
            return claimTransaction();
        }
        catch {
            return false; // Constraint violation or transaction conflict
        }
    }
    releaseResource(agentId, resource) {
        const stmt = this.db.prepare(`
      DELETE FROM work_claims
      WHERE agent_id = ? AND resource = ?
    `);
        stmt.run(agentId, resource);
    }
    releaseAllClaims(agentId) {
        const stmt = this.db.prepare(`
      DELETE FROM work_claims
      WHERE agent_id = ?
    `);
        stmt.run(agentId);
    }
    isResourceClaimed(resource) {
        const now = new Date().toISOString();
        const stmt = this.db.prepare(`
      SELECT agent_id FROM work_claims
      WHERE resource = ? AND claim_type = 'exclusive'
        AND (expires_at IS NULL OR expires_at > ?)
    `);
        const row = stmt.get(resource, now);
        return row?.agent_id || null;
    }
    getResourceClaims(resource) {
        const now = new Date().toISOString();
        const stmt = this.db.prepare(`
      SELECT id, resource, agent_id as agentId, claim_type as claimType,
             claimed_at as claimedAt, expires_at as expiresAt
      FROM work_claims
      WHERE resource = ? AND (expires_at IS NULL OR expires_at > ?)
    `);
        return stmt.all(resource, now);
    }
    getAgentClaims(agentId) {
        const stmt = this.db.prepare(`
      SELECT id, resource, agent_id as agentId, claim_type as claimType,
             claimed_at as claimedAt, expires_at as expiresAt
      FROM work_claims
      WHERE agent_id = ?
    `);
        return stmt.all(agentId);
    }
    // ==================== Work Announcements (Collaborative) ====================
    // NOTE: Agents work in isolated git worktrees, so they don't NEED to claim resources.
    // Announcements are informational - they help optimize velocity and minimize merge conflicts.
    /**
     * Announce intent to work on a resource. Does NOT lock - just informs other agents.
     * Returns overlap info if other agents are also working on related resources.
     */
    announceWork(agentId, resource, intentType, options = {}) {
        const now = new Date().toISOString();
        const agent = this.ensureAgentIsRegistered(agentId, {
            fallbackName: agentId,
            lastHeartbeat: now,
        });
        const estimatedCompletion = options.estimatedMinutes
            ? new Date(Date.now() + options.estimatedMinutes * 60000).toISOString()
            : null;
        const stmt = this.db.prepare(`
      INSERT INTO work_announcements 
        (agent_id, agent_name, worktree_branch, intent_type, resource, description, files_affected, estimated_completion, announced_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
        const agentName = agent.name || agentId;
        const worktreeBranch = agent.worktreeBranch || null;
        const result = stmt.run(agentId, agentName, worktreeBranch, intentType, resource, options.description || null, options.filesAffected ? JSON.stringify(options.filesAffected) : null, estimatedCompletion, now);
        const announcement = {
            id: result.lastInsertRowid,
            agentId,
            agentName: agent.name,
            worktreeBranch: agent.worktreeBranch,
            intentType,
            resource,
            description: options.description,
            filesAffected: options.filesAffected,
            estimatedCompletion: estimatedCompletion || undefined,
            announcedAt: now,
        };
        // Detect overlaps and generate suggestions
        const overlaps = this.detectOverlaps(resource, agentId);
        const suggestions = this.generateCollaborationSuggestions(agentId, resource, overlaps);
        // Broadcast to other agents about potential overlap
        if (overlaps.length > 0) {
            this.broadcast(agentId, 'coordination', {
                action: 'work_overlap_detected',
                resource,
                data: { overlaps, suggestions },
            });
        }
        return { announcement, overlaps, suggestions };
    }
    /**
     * Mark work as complete on a resource.
     */
    completeWork(agentId, resource) {
        const now = new Date().toISOString();
        const stmt = this.db.prepare(`
      UPDATE work_announcements
      SET completed_at = ?
      WHERE agent_id = ? AND resource = ? AND completed_at IS NULL
    `);
        stmt.run(now, agentId, resource);
        // Auto-queue a push for the agent's worktree branch if one exists
        const agentStmt = this.db.prepare(`
      SELECT worktree_branch FROM agent_registry WHERE id = ?
    `);
        const agent = agentStmt.get(agentId);
        if (agent?.worktree_branch) {
            this.queueDeploy(agentId, 'push', agent.worktree_branch, {
                remote: 'origin',
                source: 'auto-complete-work',
            });
        }
        // Broadcast completion so others know merge order
        this.broadcast(agentId, 'coordination', {
            action: 'work_completed',
            resource,
        });
    }
    /**
     * Flush all pending deploy actions via the DeployBatcher.
     * Called automatically during session cleanup / pre-compact.
     */
    async flushDeploys(options = {}) {
        const batcher = new DeployBatcher({
            dbPath: this.dbPath,
            dryRun: options.dryRun,
        });
        const results = await batcher.flushAll();
        const totals = { executed: 0, failed: 0 };
        for (const r of results) {
            totals.executed += r.executedActions;
            totals.failed += r.failedActions;
        }
        return totals;
    }
    /**
     * Get all active work announcements (not completed).
     */
    getActiveWork() {
        const stmt = this.db.prepare(`
      SELECT 
        wa.id, wa.agent_id as agentId, wa.agent_name as agentName,
        wa.worktree_branch as worktreeBranch, wa.intent_type as intentType,
        wa.resource, wa.description, wa.files_affected as filesAffected,
        wa.estimated_completion as estimatedCompletion, wa.announced_at as announcedAt
      FROM work_announcements wa
      JOIN agent_registry ar ON wa.agent_id = ar.id
      WHERE wa.completed_at IS NULL AND ar.status IN ('active', 'idle')
      ORDER BY wa.announced_at DESC
    `);
        const rows = stmt.all();
        return rows.map((row) => ({
            ...row,
            filesAffected: row.filesAffected ? JSON.parse(row.filesAffected) : undefined,
        }));
    }
    /**
     * Get work announcements for a specific resource.
     */
    getWorkOnResource(resource) {
        const stmt = this.db.prepare(`
      SELECT 
        wa.id, wa.agent_id as agentId, wa.agent_name as agentName,
        wa.worktree_branch as worktreeBranch, wa.intent_type as intentType,
        wa.resource, wa.description, wa.files_affected as filesAffected,
        wa.estimated_completion as estimatedCompletion, wa.announced_at as announcedAt
      FROM work_announcements wa
      JOIN agent_registry ar ON wa.agent_id = ar.id
      WHERE wa.resource LIKE ? AND wa.completed_at IS NULL AND ar.status IN ('active', 'idle')
      ORDER BY wa.announced_at DESC
    `);
        const rows = stmt.all(`%${resource}%`);
        return rows.map((row) => ({
            ...row,
            filesAffected: row.filesAffected ? JSON.parse(row.filesAffected) : undefined,
        }));
    }
    /**
     * Detect overlapping work that might cause merge conflicts.
     */
    detectOverlaps(resource, excludeAgentId) {
        const activeWork = this.getActiveWork();
        const overlaps = [];
        // Group by resource pattern (file, directory, or module)
        const resourceParts = resource.split('/');
        const directory = resourceParts.slice(0, -1).join('/');
        // Find work on same file
        const sameFile = activeWork.filter((w) => w.agentId !== excludeAgentId && w.resource === resource);
        // Find work on same directory
        const sameDirectory = activeWork.filter((w) => w.agentId !== excludeAgentId &&
            w.resource !== resource &&
            w.resource.startsWith(directory + '/'));
        // Find work with overlapping files
        const overlappingFiles = activeWork.filter((w) => {
            if (w.agentId === excludeAgentId)
                return false;
            if (!w.filesAffected)
                return false;
            return w.filesAffected.some((f) => f === resource || resource.includes(f) || f.includes(resource));
        });
        if (sameFile.length > 0) {
            overlaps.push({
                resource,
                agents: sameFile.map((w) => ({
                    id: w.agentId,
                    name: w.agentName || 'unknown',
                    intentType: w.intentType,
                    worktreeBranch: w.worktreeBranch,
                    description: w.description,
                })),
                conflictRisk: this.assessConflictRisk(sameFile),
                suggestion: this.generateOverlapSuggestion(sameFile, 'same_file'),
            });
        }
        if (sameDirectory.length > 0) {
            overlaps.push({
                resource: directory,
                agents: sameDirectory.map((w) => ({
                    id: w.agentId,
                    name: w.agentName || 'unknown',
                    intentType: w.intentType,
                    worktreeBranch: w.worktreeBranch,
                    description: w.description,
                })),
                conflictRisk: this.assessConflictRisk(sameDirectory, 'directory'),
                suggestion: this.generateOverlapSuggestion(sameDirectory, 'same_directory'),
            });
        }
        if (overlappingFiles.length > 0) {
            overlaps.push({
                resource: 'files_overlap',
                agents: overlappingFiles.map((w) => ({
                    id: w.agentId,
                    name: w.agentName || 'unknown',
                    intentType: w.intentType,
                    worktreeBranch: w.worktreeBranch,
                    description: w.description,
                })),
                conflictRisk: 'medium',
                suggestion: this.generateOverlapSuggestion(overlappingFiles, 'files_overlap'),
            });
        }
        return overlaps;
    }
    assessConflictRisk(work, type = 'file') {
        if (work.length === 0)
            return 'none';
        // Multiple agents editing same file = high risk
        const editors = work.filter((w) => w.intentType === 'editing' || w.intentType === 'refactoring');
        if (editors.length >= 2)
            return 'critical';
        if (editors.length === 1 && work.length > 1)
            return 'high';
        // Refactoring has higher conflict potential
        if (work.some((w) => w.intentType === 'refactoring'))
            return 'high';
        // Directory-level work is lower risk
        if (type === 'directory')
            return 'medium';
        // Review/test/document are low risk
        const lowRiskTypes = ['reviewing', 'testing', 'documenting'];
        if (work.every((w) => lowRiskTypes.includes(w.intentType)))
            return 'low';
        return 'medium';
    }
    generateOverlapSuggestion(work, type) {
        const agentNames = work.map((w) => w.agentName || w.agentId.slice(0, 8)).join(', ');
        switch (type) {
            case 'same_file':
                return (`Multiple agents (${agentNames}) working on same file. Consider: ` +
                    `1) Coordinate merge order - who finishes first should merge first, ` +
                    `2) Split into non-overlapping sections, ` +
                    `3) One agent waits for other to complete.`);
            case 'same_directory':
                return (`Agents (${agentNames}) working in same directory. Usually safe with worktrees, ` +
                    `but watch for: import changes, shared types/interfaces, barrel files (index.ts).`);
            case 'files_overlap':
                return (`Agents (${agentNames}) have overlapping file changes. Review affected files ` +
                    `and coordinate merge order to minimize conflicts.`);
            default:
                return `Overlap detected with ${agentNames}. Coordinate to optimize velocity.`;
        }
    }
    /**
     * Generate collaboration suggestions based on overlaps.
     */
    generateCollaborationSuggestions(agentId, _resource, overlaps) {
        const suggestions = [];
        for (const overlap of overlaps) {
            const allAgents = [agentId, ...overlap.agents.map((a) => a.id)];
            // Critical/High risk: suggest sequential work
            if (overlap.conflictRisk === 'critical' || overlap.conflictRisk === 'high') {
                suggestions.push({
                    type: 'sequence',
                    agents: allAgents,
                    reason: `High merge conflict risk on ${overlap.resource}. Sequential work recommended.`,
                    suggestedOrder: this.suggestMergeOrder(overlap),
                    estimatedMergeComplexity: overlap.conflictRisk,
                });
            }
            // Medium risk: suggest merge order
            if (overlap.conflictRisk === 'medium') {
                suggestions.push({
                    type: 'merge_order',
                    agents: allAgents,
                    reason: `Medium conflict risk. Agree on merge order to avoid rebase pain.`,
                    suggestedOrder: this.suggestMergeOrder(overlap),
                    estimatedMergeComplexity: 'medium',
                });
            }
            // Low risk: parallel is fine
            if (overlap.conflictRisk === 'low') {
                suggestions.push({
                    type: 'parallel',
                    agents: allAgents,
                    reason: `Low conflict risk. Parallel work is safe. Watch for shared imports.`,
                    estimatedMergeComplexity: 'low',
                });
            }
        }
        return suggestions;
    }
    suggestMergeOrder(overlap) {
        // Prefer: review/test first, then docs, then edits, then refactoring
        const priorityOrder = [
            'reviewing',
            'testing',
            'documenting',
            'editing',
            'refactoring',
        ];
        return overlap.agents
            .sort((a, b) => {
            const aPriority = priorityOrder.indexOf(a.intentType);
            const bPriority = priorityOrder.indexOf(b.intentType);
            return aPriority - bPriority;
        })
            .map((a) => a.name || a.id);
    }
    // ==================== Messaging ====================
    broadcast(fromAgent, channel, payload, priority = 5) {
        this.sendMessage(fromAgent, undefined, channel, 'notification', payload, priority);
    }
    send(fromAgent, toAgent, payload, priority = 5) {
        this.sendMessage(fromAgent, toAgent, 'direct', 'request', payload, priority);
    }
    sendMessage(fromAgent, toAgent, channel, type, payload, priority) {
        const stmt = this.db.prepare(`
      INSERT INTO agent_messages (channel, from_agent, to_agent, type, payload, priority, created_at)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `);
        stmt.run(channel, fromAgent, toAgent, type, JSON.stringify(payload), priority, new Date().toISOString());
    }
    receive(agentId, channel, markAsRead = true) {
        let sql = `
      SELECT id, channel, from_agent as fromAgent, to_agent as toAgent, type,
             payload, priority, created_at as createdAt, read_at as readAt, expires_at as expiresAt
      FROM agent_messages
      WHERE (to_agent = ? OR (to_agent IS NULL AND channel != 'direct'))
        AND read_at IS NULL
        AND (expires_at IS NULL OR expires_at > ?)
    `;
        const params = [agentId, new Date().toISOString()];
        if (channel) {
            sql += ' AND channel = ?';
            params.push(channel);
        }
        sql += ' ORDER BY priority DESC, created_at ASC';
        const stmt = this.db.prepare(sql);
        const rows = stmt.all(...params);
        const messages = rows.map((row) => ({
            ...row,
            payload: row.payload ? JSON.parse(row.payload) : {},
        }));
        if (markAsRead && messages.length > 0) {
            const ids = messages.map((m) => m.id);
            const updateStmt = this.db.prepare(`
        UPDATE agent_messages
        SET read_at = ?
        WHERE id IN (${ids.map(() => '?').join(',')})
      `);
            updateStmt.run(new Date().toISOString(), ...ids);
        }
        return messages;
    }
    getPendingMessages(agentId) {
        const stmt = this.db.prepare(`
      SELECT COUNT(*) as count
      FROM agent_messages
      WHERE (to_agent = ? OR (to_agent IS NULL AND channel != 'direct'))
        AND read_at IS NULL
        AND (expires_at IS NULL OR expires_at > ?)
    `);
        const row = stmt.get(agentId, new Date().toISOString());
        return row.count;
    }
    // ==================== Deploy Queue ====================
    queueDeploy(agentId, actionType, target, payload, options = {}) {
        const now = new Date().toISOString();
        const executeAfter = options.executeAfter?.toISOString() || new Date(Date.now() + 30000).toISOString(); // Default 30s delay for batching
        const stmt = this.db.prepare(`
      INSERT INTO deploy_queue (agent_id, action_type, target, payload, status, queued_at, execute_after, priority, dependencies)
      VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)
    `);
        const result = stmt.run(agentId, actionType, target, payload ? JSON.stringify(payload) : null, now, executeAfter, options.priority || 5, options.dependencies ? JSON.stringify(options.dependencies) : null);
        return result.lastInsertRowid;
    }
    getPendingDeploys() {
        const stmt = this.db.prepare(`
      SELECT id, agent_id as agentId, action_type as actionType, target, payload,
             status, batch_id as batchId, queued_at as queuedAt, 
             execute_after as executeAfter, priority, dependencies
      FROM deploy_queue
      WHERE status = 'pending'
      ORDER BY priority DESC, queued_at ASC
    `);
        const rows = stmt.all();
        return rows.map((row) => ({
            ...row,
            payload: row.payload ? JSON.parse(row.payload) : undefined,
            dependencies: row.dependencies ? JSON.parse(row.dependencies) : undefined,
        }));
    }
    getReadyDeploys() {
        const now = new Date().toISOString();
        const stmt = this.db.prepare(`
      SELECT id, agent_id as agentId, action_type as actionType, target, payload,
             status, batch_id as batchId, queued_at as queuedAt, 
             execute_after as executeAfter, priority, dependencies
      FROM deploy_queue
      WHERE status = 'pending' AND execute_after <= ?
      ORDER BY priority DESC, queued_at ASC
    `);
        const rows = stmt.all(now);
        return rows.map((row) => ({
            ...row,
            payload: row.payload ? JSON.parse(row.payload) : undefined,
            dependencies: row.dependencies ? JSON.parse(row.dependencies) : undefined,
        }));
    }
    updateDeployStatus(deployId, status, batchId) {
        const stmt = this.db.prepare(`
      UPDATE deploy_queue
      SET status = ?, batch_id = ?
      WHERE id = ?
    `);
        stmt.run(status, batchId || null, deployId);
    }
    // ==================== Status ====================
    getStatus() {
        const activeAgents = this.getActiveAgents();
        const claimsStmt = this.db.prepare(`
      SELECT id, resource, agent_id as agentId, claim_type as claimType,
             claimed_at as claimedAt, expires_at as expiresAt
      FROM work_claims
      WHERE expires_at IS NULL OR expires_at > ?
    `);
        const activeClaims = claimsStmt.all(new Date().toISOString());
        const pendingDeploys = this.getPendingDeploys();
        // Count pending messages (broadcast + unclaimed)
        const msgStmt = this.db.prepare(`
      SELECT COUNT(*) as count
      FROM agent_messages
      WHERE read_at IS NULL AND (expires_at IS NULL OR expires_at > ?)
    `);
        const msgRow = msgStmt.get(new Date().toISOString());
        return {
            activeAgents,
            activeClaims,
            pendingDeploys,
            pendingMessages: msgRow.count,
        };
    }
    // ==================== Cleanup ====================
    cleanup() {
        const cutoff = new Date(Date.now() - 86400000).toISOString(); // 24 hours ago
        // Clean old messages
        this.db.prepare(`DELETE FROM agent_messages WHERE created_at < ?`).run(cutoff);
        // Clean expired claims
        this.db.prepare(`DELETE FROM work_claims WHERE expires_at < ?`).run(new Date().toISOString());
        // Clean old completed agents
        this.db
            .prepare(`
      DELETE FROM agent_registry
      WHERE status IN ('completed', 'failed') AND started_at < ?
    `)
            .run(cutoff);
        // Clean old completed deploys
        this.db
            .prepare(`
      DELETE FROM deploy_queue
      WHERE status IN ('completed', 'failed') AND queued_at < ?
    `)
            .run(cutoff);
    }
    ensureAgentIsRegistered(agentId, options = {}) {
        const existing = this.getAgent(agentId);
        const now = options.lastHeartbeat || new Date().toISOString();
        if (!existing) {
            this.db
                .prepare(`
        INSERT INTO agent_registry (id, name, session_id, status, worktree_branch, started_at, last_heartbeat, capabilities)
        VALUES (?, ?, ?, 'active', ?, ?, ?, ?)
      `)
                .run(agentId, options.fallbackName || agentId, this.sessionId, options.worktreeBranch || null, now, now, null);
            return {
                id: agentId,
                name: options.fallbackName || agentId,
                sessionId: this.sessionId,
                status: 'active',
                worktreeBranch: options.worktreeBranch || undefined,
                startedAt: now,
                lastHeartbeat: now,
            };
        }
        if (existing.status === 'completed' || existing.status === 'failed') {
            this.db
                .prepare(`
        UPDATE agent_registry
        SET status = 'active', current_task = NULL, last_heartbeat = ?
        WHERE id = ?
      `)
                .run(now, agentId);
            return {
                ...existing,
                status: 'active',
                currentTask: undefined,
                lastHeartbeat: now,
            };
        }
        this.db
            .prepare(`
      UPDATE agent_registry
      SET last_heartbeat = ?
      WHERE id = ?
    `)
            .run(now, agentId);
        return {
            ...existing,
            lastHeartbeat: now,
        };
    }
}
//# sourceMappingURL=service.js.map