/**
 * Health Steward Service
 *
 * This service monitors agent health and detects stuck agents.
 * The Health Steward detects problematic agents and takes corrective action.
 *
 * Key features:
 * - Detect agents with no output for configurable duration
 * - Detect agents with repeated errors
 * - Detect crashed agent processes
 * - Attempt to unstick agents
 * - Notify Director of issues
 * - Stop and reassign tasks from stuck agents
 *
 * TB-O24: Health Steward Implementation
 *
 * @module
 */
import { EventEmitter } from 'node:events';
import { createTimestamp, TaskStatus } from '@stoneforge/core';
import { getAgentMetadata } from './agent-registry.js';
/**
 * All valid health issue types
 */
export const HealthIssueTypes = [
    'no_output',
    'repeated_errors',
    'process_crashed',
    'high_error_rate',
    'session_stale',
    'unresponsive',
];
/**
 * Type guard for HealthIssueType
 */
export function isHealthIssueType(value) {
    return typeof value === 'string' && HealthIssueTypes.includes(value);
}
/**
 * All valid severity levels
 */
export const HealthIssueSeverities = ['warning', 'error', 'critical'];
/**
 * Type guard for HealthIssueSeverity
 */
export function isHealthIssueSeverity(value) {
    return typeof value === 'string' && HealthIssueSeverities.includes(value);
}
/**
 * All valid health actions
 */
export const HealthActions = [
    'monitor',
    'send_ping',
    'restart',
    'notify_director',
    'reassign_task',
    'escalate',
];
/**
 * Type guard for HealthAction
 */
export function isHealthAction(value) {
    return typeof value === 'string' && HealthActions.includes(value);
}
// ============================================================================
// Health Steward Service Implementation
// ============================================================================
/**
 * Default configuration values
 */
const DEFAULT_CONFIG = {
    noOutputThresholdMs: 5 * 60 * 1000, // 5 minutes
    errorCountThreshold: 5,
    errorWindowMs: 10 * 60 * 1000, // 10 minutes
    staleSessionThresholdMs: 15 * 60 * 1000, // 15 minutes
    healthCheckIntervalMs: 60 * 1000, // 1 minute
    maxPingAttempts: 3,
    autoRestart: true,
    autoReassign: true,
    notifyDirector: true,
    maxIssuesPerAgent: 10,
};
/**
 * Implementation of the Health Steward Service.
 */
export class HealthStewardServiceImpl {
    // Reserved for future persistence of health data
    _api;
    config;
    agentRegistry;
    sessionManager;
    taskAssignment;
    dispatchService;
    emitter;
    // State
    running = false;
    checkInterval;
    issueCounter = 0;
    // Tracking
    activityTrackers = new Map();
    activeIssues = new Map();
    resolvedIssueIds = new Set();
    // Statistics
    totalChecks = 0;
    totalIssuesDetected = 0;
    totalIssuesResolved = 0;
    totalActionsTaken = 0;
    successfulActions = 0;
    failedActions = 0;
    constructor(api, agentRegistry, sessionManager, taskAssignment, dispatchService, config = {}) {
        this._api = api;
        this.agentRegistry = agentRegistry;
        this.sessionManager = sessionManager;
        this.taskAssignment = taskAssignment;
        this.dispatchService = dispatchService;
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.emitter = new EventEmitter();
    }
    // ----------------------------------------
    // Health Checks
    // ----------------------------------------
    async runHealthCheck() {
        const startTime = Date.now();
        const timestamp = createTimestamp();
        const newIssues = [];
        const resolvedIssues = [];
        const actionsTaken = [];
        // Get all agents with running sessions
        const agents = await this.getRunningAgents();
        let agentsWithIssues = 0;
        for (const agent of agents) {
            const agentId = agent.id;
            const status = await this.checkAgent(agentId);
            if (!status.isHealthy) {
                agentsWithIssues++;
                // Record new issues
                for (const issue of status.issues) {
                    if (!this.activeIssues.has(issue.id)) {
                        newIssues.push(issue);
                        this.emitter.emit('issue:detected', issue);
                    }
                }
                // Take automatic actions if configured
                for (const issue of status.issues) {
                    const action = await this.determineAction(issue);
                    if (action !== 'monitor') {
                        const result = await this.takeAction(issue.id, action);
                        actionsTaken.push(result);
                    }
                }
            }
        }
        // Check for resolved issues
        for (const [issueId, issue] of this.activeIssues) {
            const agent = await this.agentRegistry.getAgent(issue.agentId);
            if (!agent) {
                // Agent no longer exists - resolve issue
                this.resolveIssue(issueId);
                resolvedIssues.push(issueId);
                continue;
            }
            // Check if the issue condition still applies
            const stillApplies = await this.issueStillApplies(issue);
            if (!stillApplies) {
                this.resolveIssue(issueId);
                resolvedIssues.push(issueId);
            }
        }
        this.totalChecks++;
        const result = {
            timestamp,
            agentsChecked: agents.length,
            agentsWithIssues,
            newIssues,
            resolvedIssues,
            actionsTaken,
            durationMs: Date.now() - startTime,
        };
        this.emitter.emit('check:completed', result);
        return result;
    }
    async checkAgent(agentId) {
        const agent = await this.agentRegistry.getAgent(agentId);
        if (!agent) {
            return this.createUnhealthyStatus(agentId, 'unknown', 'worker', []);
        }
        const meta = getAgentMetadata(agent);
        if (!meta) {
            return this.createUnhealthyStatus(agentId, agent.name, 'worker', []);
        }
        const tracker = this.getOrCreateTracker(agentId);
        tracker.lastHealthCheckAt = createTimestamp();
        const issues = [];
        const now = Date.now();
        // Get session info
        const session = this.sessionManager.getActiveSession(agentId);
        const sessionStatus = session?.status ?? meta.sessionStatus ?? 'idle';
        // Only check running sessions
        if (sessionStatus === 'running') {
            // Check for no output
            if (tracker.lastOutputAt) {
                const lastOutputTime = this.getTimestampMs(tracker.lastOutputAt);
                const timeSinceOutput = now - lastOutputTime;
                if (timeSinceOutput > this.config.noOutputThresholdMs) {
                    issues.push(this.createIssue(agentId, agent.name, meta.agentRole, 'no_output', {
                        timeSinceOutputMs: timeSinceOutput,
                        thresholdMs: this.config.noOutputThresholdMs,
                        sessionId: session?.id,
                    }));
                }
            }
            // Check for repeated errors
            const recentErrors = this.countRecentEvents(tracker.errorTimestamps, this.config.errorWindowMs);
            if (recentErrors >= this.config.errorCountThreshold) {
                issues.push(this.createIssue(agentId, agent.name, meta.agentRole, 'repeated_errors', {
                    errorCount: recentErrors,
                    thresholdCount: this.config.errorCountThreshold,
                    windowMs: this.config.errorWindowMs,
                    sessionId: session?.id,
                }));
            }
            // Check for high error rate
            const recentOutputs = this.countRecentEvents(tracker.outputTimestamps, this.config.errorWindowMs);
            if (recentOutputs > 0 && recentErrors > 0) {
                const errorRate = recentErrors / (recentErrors + recentOutputs);
                if (errorRate > 0.5) { // More than 50% errors
                    issues.push(this.createIssue(agentId, agent.name, meta.agentRole, 'high_error_rate', {
                        errorRate,
                        errorCount: recentErrors,
                        outputCount: recentOutputs,
                        sessionId: session?.id,
                    }));
                }
            }
            // Check for stale session
            if (session?.lastActivityAt) {
                const lastActivityTime = this.getTimestampMs(session.lastActivityAt);
                const timeSinceActivity = now - lastActivityTime;
                if (timeSinceActivity > this.config.staleSessionThresholdMs) {
                    issues.push(this.createIssue(agentId, agent.name, meta.agentRole, 'session_stale', {
                        timeSinceActivityMs: timeSinceActivity,
                        thresholdMs: this.config.staleSessionThresholdMs,
                        sessionId: session.id,
                    }));
                }
            }
        }
        // Get current task
        const agentTasks = await this.taskAssignment.getAgentTasks(agentId, {
            taskStatus: TaskStatus.IN_PROGRESS,
        });
        const currentTaskId = agentTasks[0]?.taskId;
        // Add task context to issues
        for (const issue of issues) {
            if (currentTaskId) {
                issue.taskId = currentTaskId;
            }
        }
        // Update active issues
        for (const issue of issues) {
            const existingIssue = this.findExistingIssue(agentId, issue.issueType);
            if (existingIssue) {
                // Update existing issue
                this.activeIssues.set(existingIssue.id, {
                    ...existingIssue,
                    lastSeenAt: createTimestamp(),
                    occurrenceCount: existingIssue.occurrenceCount + 1,
                    context: issue.context,
                });
            }
            else {
                // New issue
                this.activeIssues.set(issue.id, issue);
                this.totalIssuesDetected++;
            }
        }
        const recentErrors = this.countRecentEvents(tracker.errorTimestamps, this.config.errorWindowMs);
        const recentOutputs = this.countRecentEvents(tracker.outputTimestamps, this.config.errorWindowMs);
        return {
            agentId,
            agentName: agent.name,
            agentRole: meta.agentRole,
            isHealthy: issues.length === 0,
            issues,
            lastActivityAt: tracker.lastOutputAt ?? meta.lastActivityAt,
            lastHealthCheckAt: tracker.lastHealthCheckAt,
            sessionStatus,
            currentTaskId,
            recentErrorCount: recentErrors,
            recentOutputCount: recentOutputs,
        };
    }
    async getAllAgentHealth() {
        const agents = await this.getRunningAgents();
        const statuses = [];
        for (const agent of agents) {
            const agentId = agent.id;
            const status = await this.checkAgent(agentId);
            statuses.push(status);
        }
        return statuses;
    }
    // ----------------------------------------
    // Issue Management
    // ----------------------------------------
    getActiveIssues() {
        return Array.from(this.activeIssues.values());
    }
    getIssuesForAgent(agentId) {
        return Array.from(this.activeIssues.values()).filter((issue) => issue.agentId === agentId);
    }
    resolveIssue(issueId) {
        const issue = this.activeIssues.get(issueId);
        if (!issue) {
            return false;
        }
        this.activeIssues.delete(issueId);
        this.resolvedIssueIds.add(issueId);
        this.totalIssuesResolved++;
        this.emitter.emit('issue:resolved', issueId);
        return true;
    }
    clearResolvedIssues() {
        this.resolvedIssueIds.clear();
    }
    // ----------------------------------------
    // Actions
    // ----------------------------------------
    async takeAction(issueId, action) {
        const issue = this.activeIssues.get(issueId);
        if (!issue) {
            return {
                success: false,
                action: action ?? 'monitor',
                issueId,
                message: `Issue not found: ${issueId}`,
                actionTakenAt: createTimestamp(),
                error: 'Issue not found',
            };
        }
        const effectiveAction = action ?? await this.determineAction(issue);
        this.totalActionsTaken++;
        let result;
        try {
            switch (effectiveAction) {
                case 'monitor':
                    result = {
                        success: true,
                        action: effectiveAction,
                        issueId,
                        message: `Continuing to monitor agent ${issue.agentName}`,
                        actionTakenAt: createTimestamp(),
                    };
                    break;
                case 'send_ping':
                    const pingSuccess = await this.pingAgent(issue.agentId);
                    result = {
                        success: pingSuccess,
                        action: effectiveAction,
                        issueId,
                        message: pingSuccess
                            ? `Agent ${issue.agentName} responded to ping`
                            : `Agent ${issue.agentName} did not respond to ping`,
                        actionTakenAt: createTimestamp(),
                    };
                    break;
                case 'restart':
                    const restartSuccess = await this.restartAgent(issue.agentId);
                    result = {
                        success: restartSuccess,
                        action: effectiveAction,
                        issueId,
                        message: restartSuccess
                            ? `Successfully restarted agent ${issue.agentName}`
                            : `Failed to restart agent ${issue.agentName}`,
                        actionTakenAt: createTimestamp(),
                    };
                    if (restartSuccess) {
                        this.resolveIssue(issueId);
                    }
                    break;
                case 'notify_director':
                    const notifySuccess = await this.notifyDirector(issue);
                    result = {
                        success: notifySuccess,
                        action: effectiveAction,
                        issueId,
                        message: notifySuccess
                            ? `Director notified about ${issue.agentName}`
                            : `Failed to notify Director about ${issue.agentName}`,
                        actionTakenAt: createTimestamp(),
                    };
                    break;
                case 'reassign_task':
                    if (!issue.taskId) {
                        result = {
                            success: false,
                            action: effectiveAction,
                            issueId,
                            message: `No task to reassign for agent ${issue.agentName}`,
                            actionTakenAt: createTimestamp(),
                            error: 'No task associated with issue',
                        };
                    }
                    else {
                        const reassignResult = await this.reassignTask(issue.agentId, issue.taskId);
                        result = {
                            success: reassignResult.success,
                            action: effectiveAction,
                            issueId,
                            message: reassignResult.success
                                ? `Task reassigned from ${issue.agentName} to new agent`
                                : `Failed to reassign task from ${issue.agentName}`,
                            actionTakenAt: createTimestamp(),
                            newAgentId: reassignResult.newAgentId,
                            error: reassignResult.error,
                        };
                        if (reassignResult.success) {
                            this.resolveIssue(issueId);
                        }
                    }
                    break;
                case 'escalate':
                    // For escalate, we notify both Director and mark for human review
                    await this.notifyDirector(issue);
                    result = {
                        success: true,
                        action: effectiveAction,
                        issueId,
                        message: `Issue escalated for agent ${issue.agentName} - requires human intervention`,
                        actionTakenAt: createTimestamp(),
                    };
                    break;
                default:
                    result = {
                        success: false,
                        action: effectiveAction,
                        issueId,
                        message: `Unknown action: ${effectiveAction}`,
                        actionTakenAt: createTimestamp(),
                        error: `Unknown action: ${effectiveAction}`,
                    };
            }
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            result = {
                success: false,
                action: effectiveAction,
                issueId,
                message: `Action failed: ${errorMessage}`,
                actionTakenAt: createTimestamp(),
                error: errorMessage,
            };
        }
        if (result.success) {
            this.successfulActions++;
        }
        else {
            this.failedActions++;
        }
        this.emitter.emit('action:taken', result);
        return result;
    }
    async pingAgent(agentId) {
        const tracker = this.getOrCreateTracker(agentId);
        // Check if we've exceeded ping attempts
        if (tracker.pingAttempts >= this.config.maxPingAttempts) {
            return false;
        }
        const session = this.sessionManager.getActiveSession(agentId);
        if (!session || session.status !== 'running') {
            return false;
        }
        tracker.lastPingAt = createTimestamp();
        tracker.pingAttempts++;
        // Send a health check message via the session
        const result = await this.sessionManager.messageSession(session.id, {
            content: 'Health check ping - please respond with any message to confirm you are active.',
        });
        return result.success;
    }
    async restartAgent(agentId) {
        const session = this.sessionManager.getActiveSession(agentId);
        if (session) {
            try {
                // Stop the current session
                await this.sessionManager.stopSession(session.id, {
                    graceful: true,
                    reason: 'Health Steward restart due to detected issues',
                });
            }
            catch {
                // Continue even if stop fails
            }
        }
        // Reset tracker
        const tracker = this.getOrCreateTracker(agentId);
        tracker.pingAttempts = 0;
        tracker.errorTimestamps = [];
        // Note: We don't auto-start a new session here - that's the responsibility
        // of the agent or Director. We've cleared the issues by stopping.
        return true;
    }
    async notifyDirector(issue) {
        // Find the Director agent
        const directors = await this.agentRegistry.getAgentsByRole('director');
        if (directors.length === 0) {
            return false;
        }
        const director = directors[0];
        const directorId = director.id;
        // Build notification content
        const severity = issue.severity.toUpperCase();
        const content = [
            `# Health Alert: ${severity}`,
            '',
            `**Agent:** ${issue.agentName} (${issue.agentRole})`,
            `**Issue:** ${issue.issueType}`,
            `**Description:** ${issue.description}`,
            '',
            `**Detected:** ${issue.detectedAt}`,
            `**Occurrences:** ${issue.occurrenceCount}`,
            '',
            issue.taskId ? `**Current Task:** ${issue.taskId}` : '',
            '',
            '**Recommended Action:**',
            this.getRecommendedActionDescription(issue),
        ].filter(Boolean).join('\n');
        try {
            // Use task-assignment type as a general notification mechanism
            await this.dispatchService.notifyAgent(directorId, 'task-assignment', content, {
                healthAlert: true,
                issueId: issue.id,
                issueType: issue.issueType,
                severity: issue.severity,
                agentId: issue.agentId,
                agentName: issue.agentName,
                taskId: issue.taskId,
            });
            return true;
        }
        catch {
            return false;
        }
    }
    async reassignTask(agentId, taskId) {
        try {
            // Stop the current agent session
            const session = this.sessionManager.getActiveSession(agentId);
            if (session) {
                await this.sessionManager.stopSession(session.id, {
                    graceful: false,
                    reason: 'Task reassignment due to agent health issues',
                });
            }
            // Unassign the task - it will be picked up by the dispatch daemon
            await this.taskAssignment.unassignTask(taskId);
            // Task reassignment is now handled by the dispatch daemon polling for
            // unassigned tasks, rather than immediately dispatching here
            return {
                success: true,
                // Note: newAgentId is undefined since dispatch daemon handles assignment
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                success: false,
                error: errorMessage,
            };
        }
    }
    // ----------------------------------------
    // Activity Tracking
    // ----------------------------------------
    recordOutput(agentId) {
        const tracker = this.getOrCreateTracker(agentId);
        const now = createTimestamp();
        tracker.lastOutputAt = now;
        tracker.outputTimestamps.push(now);
        // Reset ping attempts on successful output
        tracker.pingAttempts = 0;
        // Prune old timestamps
        this.pruneTimestamps(tracker);
    }
    recordError(agentId, _error) {
        const tracker = this.getOrCreateTracker(agentId);
        const now = createTimestamp();
        tracker.lastErrorAt = now;
        tracker.errorTimestamps.push(now);
        // Prune old timestamps
        this.pruneTimestamps(tracker);
    }
    recordCrash(agentId, exitCode) {
        const agent = this.activityTrackers.get(agentId);
        const agentName = agent ? 'Agent' : 'Unknown';
        // Create a crash issue immediately
        const issue = this.createIssue(agentId, agentName, 'worker', // Default, will be updated
        'process_crashed', { exitCode });
        this.activeIssues.set(issue.id, issue);
        this.totalIssuesDetected++;
        this.emitter.emit('issue:detected', issue);
    }
    // ----------------------------------------
    // Lifecycle
    // ----------------------------------------
    start() {
        if (this.running) {
            return;
        }
        this.running = true;
        this.checkInterval = setInterval(() => this.runHealthCheck().catch(() => { }), this.config.healthCheckIntervalMs);
    }
    stop() {
        if (!this.running) {
            return;
        }
        this.running = false;
        if (this.checkInterval) {
            clearInterval(this.checkInterval);
            this.checkInterval = undefined;
        }
    }
    isRunning() {
        return this.running;
    }
    // ----------------------------------------
    // Statistics
    // ----------------------------------------
    getStats() {
        return {
            totalChecks: this.totalChecks,
            totalIssuesDetected: this.totalIssuesDetected,
            totalIssuesResolved: this.totalIssuesResolved,
            totalActionsTaken: this.totalActionsTaken,
            successfulActions: this.successfulActions,
            failedActions: this.failedActions,
            activeIssues: this.activeIssues.size,
            monitoredAgents: this.activityTrackers.size,
        };
    }
    on(event, listener) {
        this.emitter.on(event, listener);
    }
    off(event, listener) {
        this.emitter.off(event, listener);
    }
    // ----------------------------------------
    // Private Helpers
    // ----------------------------------------
    async getRunningAgents() {
        const allAgents = await this.agentRegistry.listAgents({
            sessionStatus: 'running',
        });
        return allAgents;
    }
    getOrCreateTracker(agentId) {
        let tracker = this.activityTrackers.get(agentId);
        if (!tracker) {
            tracker = {
                agentId,
                errorTimestamps: [],
                outputTimestamps: [],
                pingAttempts: 0,
            };
            this.activityTrackers.set(agentId, tracker);
        }
        return tracker;
    }
    createIssue(agentId, agentName, agentRole, issueType, context) {
        const now = createTimestamp();
        const severity = this.determineSeverity(issueType, context);
        return {
            id: `health-${++this.issueCounter}-${Date.now()}`,
            agentId,
            agentName,
            agentRole,
            issueType,
            severity,
            description: this.getIssueDescription(issueType, context),
            detectedAt: now,
            lastSeenAt: now,
            occurrenceCount: 1,
            sessionId: context?.sessionId,
            context,
        };
    }
    createUnhealthyStatus(agentId, agentName, agentRole, issues) {
        return {
            agentId,
            agentName,
            agentRole,
            isHealthy: false,
            issues,
            recentErrorCount: 0,
            recentOutputCount: 0,
        };
    }
    determineSeverity(issueType, context) {
        switch (issueType) {
            case 'process_crashed':
                return 'critical';
            case 'repeated_errors':
                const errorCount = context?.errorCount ?? 0;
                return errorCount > 10 ? 'critical' : 'error';
            case 'high_error_rate':
                return 'error';
            case 'no_output':
                const timeSinceOutput = context?.timeSinceOutputMs ?? 0;
                return timeSinceOutput > 15 * 60 * 1000 ? 'error' : 'warning';
            case 'session_stale':
                return 'warning';
            case 'unresponsive':
                return 'error';
            default:
                return 'warning';
        }
    }
    getIssueDescription(issueType, context) {
        switch (issueType) {
            case 'no_output':
                const mins = Math.round((context?.timeSinceOutputMs ?? 0) / 60000);
                return `Agent has produced no output for ${mins} minutes`;
            case 'repeated_errors':
                return `Agent has encountered ${context?.errorCount ?? 'multiple'} errors in the last ${Math.round((context?.windowMs ?? 600000) / 60000)} minutes`;
            case 'process_crashed':
                return `Agent process crashed with exit code ${context?.exitCode ?? 'unknown'}`;
            case 'high_error_rate':
                const rate = (context?.errorRate ?? 0) * 100;
                return `Agent has a ${rate.toFixed(1)}% error rate`;
            case 'session_stale':
                const staleMins = Math.round((context?.timeSinceActivityMs ?? 0) / 60000);
                return `Agent session has been inactive for ${staleMins} minutes`;
            case 'unresponsive':
                return 'Agent is not responding to health check pings';
            default:
                return 'Unknown health issue';
        }
    }
    async determineAction(issue) {
        const tracker = this.activityTrackers.get(issue.agentId);
        switch (issue.issueType) {
            case 'process_crashed':
                // For crashes, try to reassign if auto-reassign is enabled
                if (this.config.autoReassign && issue.taskId) {
                    return 'reassign_task';
                }
                return 'notify_director';
            case 'no_output':
            case 'session_stale':
                // First try pinging
                if (tracker && tracker.pingAttempts < this.config.maxPingAttempts) {
                    return 'send_ping';
                }
                // Then try restart
                if (this.config.autoRestart) {
                    return 'restart';
                }
                return 'notify_director';
            case 'repeated_errors':
            case 'high_error_rate':
                // Notify Director about error patterns
                if (this.config.notifyDirector) {
                    return 'notify_director';
                }
                return 'monitor';
            case 'unresponsive':
                // Escalate unresponsive agents
                if (issue.severity === 'critical') {
                    return 'escalate';
                }
                if (this.config.autoRestart) {
                    return 'restart';
                }
                return 'notify_director';
            default:
                return 'monitor';
        }
    }
    getRecommendedActionDescription(issue) {
        switch (issue.issueType) {
            case 'process_crashed':
                return 'Restart the agent and reassign any incomplete tasks.';
            case 'no_output':
                return 'Check if the agent is stuck and consider restarting.';
            case 'repeated_errors':
                return 'Review the task requirements and agent logs to identify the root cause.';
            case 'high_error_rate':
                return 'The agent may be struggling with the current task. Consider reassignment.';
            case 'session_stale':
                return 'The agent session may be hung. Try restarting the session.';
            case 'unresponsive':
                return 'The agent is not responding. Force terminate and reassign task.';
            default:
                return 'Monitor the situation and take action if it persists.';
        }
    }
    findExistingIssue(agentId, issueType) {
        for (const issue of this.activeIssues.values()) {
            if (issue.agentId === agentId && issue.issueType === issueType) {
                return issue;
            }
        }
        return undefined;
    }
    async issueStillApplies(issue) {
        const tracker = this.activityTrackers.get(issue.agentId);
        if (!tracker) {
            return false;
        }
        const now = Date.now();
        switch (issue.issueType) {
            case 'no_output':
                if (!tracker.lastOutputAt)
                    return true;
                const timeSinceOutput = now - this.getTimestampMs(tracker.lastOutputAt);
                return timeSinceOutput > this.config.noOutputThresholdMs;
            case 'repeated_errors':
                const recentErrors = this.countRecentEvents(tracker.errorTimestamps, this.config.errorWindowMs);
                return recentErrors >= this.config.errorCountThreshold;
            case 'process_crashed':
                // Crashes are resolved by explicit restart/reassign
                return true;
            case 'high_error_rate':
                const errors = this.countRecentEvents(tracker.errorTimestamps, this.config.errorWindowMs);
                const outputs = this.countRecentEvents(tracker.outputTimestamps, this.config.errorWindowMs);
                if (outputs === 0)
                    return false;
                return (errors / (errors + outputs)) > 0.5;
            case 'session_stale':
                const session = this.sessionManager.getActiveSession(issue.agentId);
                if (!session?.lastActivityAt)
                    return false;
                const timeSinceActivity = now - this.getTimestampMs(session.lastActivityAt);
                return timeSinceActivity > this.config.staleSessionThresholdMs;
            default:
                return false;
        }
    }
    countRecentEvents(timestamps, windowMs) {
        const now = Date.now();
        const windowStart = now - windowMs;
        return timestamps.filter((t) => this.getTimestampMs(t) >= windowStart).length;
    }
    pruneTimestamps(tracker) {
        const now = Date.now();
        const windowStart = now - this.config.errorWindowMs;
        tracker.errorTimestamps = tracker.errorTimestamps.filter((t) => this.getTimestampMs(t) >= windowStart);
        tracker.outputTimestamps = tracker.outputTimestamps.filter((t) => this.getTimestampMs(t) >= windowStart);
    }
    getTimestampMs(timestamp) {
        return typeof timestamp === 'number' ? timestamp : new Date(timestamp).getTime();
    }
}
// ============================================================================
// Factory Function
// ============================================================================
/**
 * Creates a HealthStewardService instance
 */
export function createHealthStewardService(api, agentRegistry, sessionManager, taskAssignment, dispatchService, config) {
    return new HealthStewardServiceImpl(api, agentRegistry, sessionManager, taskAssignment, dispatchService, config);
}
//# sourceMappingURL=health-steward-service.js.map