"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RuntimeService = void 0;
const common_1 = require("@nestjs/common");
const contracts_1 = require("@teachagenthub/contracts");
const guard_service_1 = require("../../guard/service/guard.service");
const memory_service_1 = require("../../memory/service/memory.service");
const aubo_mcp_service_1 = require("../../mcp/service/aubo-mcp.service");
const registry_service_1 = require("../../registry/service/registry.service");
let RuntimeService = class RuntimeService {
    guard;
    memory;
    registry;
    auboMcp;
    tasks = new Map();
    constructor(guard, memory, registry, auboMcp) {
        this.guard = guard;
        this.memory = memory;
        this.registry = registry;
        this.auboMcp = auboMcp;
    }
    async createTask(command, access) {
        const now = new Date().toISOString();
        const task = {
            taskId: `task_${Math.random().toString(36).slice(2, 10)}`,
            sessionId: command.sessionId,
            status: 'running',
            createdAt: now,
            updatedAt: now
        };
        const resolveResult = await this.registry.resolveSkills({
            command,
            tenantId: access?.tenantId,
            userId: access?.userId,
            userRole: access?.userRole
        });
        const skillById = new Map(resolveResult.skills.map((skill) => [skill.skillId, skill]));
        const traces = [];
        const reportStartedAt = new Date();
        const traceBySkill = new Map();
        const pending = new Set(resolveResult.dag.map((node) => node.skillId));
        while (pending.size > 0) {
            const ready = resolveResult.dag.filter((node) => {
                if (!pending.has(node.skillId)) {
                    return false;
                }
                return node.dependencies.every((dep) => traceBySkill.has(dep));
            });
            if (ready.length === 0) {
                for (const nodeId of pending) {
                    const fallbackNode = resolveResult.dag.find((item) => item.skillId === nodeId);
                    if (!fallbackNode) {
                        continue;
                    }
                    const nowIso = new Date().toISOString();
                    const trace = {
                        skillId: fallbackNode.skillId,
                        module: fallbackNode.module,
                        version: fallbackNode.version,
                        status: 'skipped',
                        attempts: 0,
                        startedAt: nowIso,
                        endedAt: nowIso,
                        durationMs: 0,
                        timeoutMs: fallbackNode.timeoutMs,
                        input: this.createSkillInput(command, fallbackNode.skillId, fallbackNode.module),
                        error: 'unresolved DAG dependency'
                    };
                    traces.push(trace);
                    traceBySkill.set(trace.skillId, trace);
                    pending.delete(trace.skillId);
                    void this.reportSkillTrace(command.sessionId, task.taskId, trace);
                }
                task.status = 'failed';
                task.error = 'runtime DAG resolution failed';
                break;
            }
            const skippedInLevel = [];
            const runnableInLevel = [];
            for (const node of ready) {
                const failedDependency = node.dependencies.some((dep) => {
                    const dependencyTrace = traceBySkill.get(dep);
                    return dependencyTrace ? dependencyTrace.status !== 'success' : false;
                });
                if (failedDependency) {
                    const nowIso = new Date().toISOString();
                    const trace = {
                        skillId: node.skillId,
                        module: node.module,
                        version: node.version,
                        status: 'skipped',
                        attempts: 0,
                        startedAt: nowIso,
                        endedAt: nowIso,
                        durationMs: 0,
                        timeoutMs: node.timeoutMs,
                        input: this.createSkillInput(command, node.skillId, node.module),
                        error: 'dependency not satisfied'
                    };
                    skippedInLevel.push(trace);
                    continue;
                }
                const skill = skillById.get(node.skillId);
                if (!skill) {
                    const nowIso = new Date().toISOString();
                    skippedInLevel.push({
                        skillId: node.skillId,
                        module: node.module,
                        version: node.version,
                        status: 'skipped',
                        attempts: 0,
                        startedAt: nowIso,
                        endedAt: nowIso,
                        durationMs: 0,
                        timeoutMs: node.timeoutMs,
                        input: this.createSkillInput(command, node.skillId, node.module),
                        error: 'skill metadata missing'
                    });
                    continue;
                }
                runnableInLevel.push({ node, skill });
            }
            for (const skippedTrace of skippedInLevel) {
                traces.push(skippedTrace);
                traceBySkill.set(skippedTrace.skillId, skippedTrace);
                pending.delete(skippedTrace.skillId);
                void this.reportSkillTrace(command.sessionId, task.taskId, skippedTrace);
            }
            const levelResults = await Promise.all(runnableInLevel.map(async ({ node, skill }) => {
                await this.reportSkillStart(command.sessionId, task.taskId, node, command);
                return this.executeSkillWithRetry(skill, command);
            }));
            for (const trace of levelResults) {
                traces.push(trace);
                traceBySkill.set(trace.skillId, trace);
                pending.delete(trace.skillId);
                await this.reportSkillTrace(command.sessionId, task.taskId, trace);
                if (trace.status === 'failed') {
                    task.status = 'failed';
                    task.error = trace.error ?? `skill failed: ${trace.skillId}`;
                }
            }
        }
        if (task.status !== 'failed' && traces.every((trace) => trace.status === 'success')) {
            task.status = 'completed';
        }
        else if (task.status !== 'failed') {
            task.status = 'failed';
            task.error = 'one or more skills were skipped';
        }
        const reportEndedAt = new Date();
        const report = {
            framework: resolveResult.framework,
            startedAt: reportStartedAt.toISOString(),
            endedAt: reportEndedAt.toISOString(),
            totalDurationMs: reportEndedAt.getTime() - reportStartedAt.getTime(),
            traces
        };
        task.executionReport = report;
        task.updatedAt = reportEndedAt.toISOString();
        this.memory.append(command.sessionId, `task ${task.status}: ${task.taskId}`);
        this.tasks.set(task.taskId, task);
        return task;
    }
    async executeSkillWithRetry(skill, command) {
        const startedAt = new Date();
        const attempts = Math.max(skill.executionPolicy.retry.maxAttempts, 1);
        let lastError = 'unknown error';
        for (let attempt = 1; attempt <= attempts; attempt += 1) {
            const attemptStartedAt = Date.now();
            try {
                const output = await this.executeSkillWithTimeout(skill, command);
                const endedAt = new Date();
                return {
                    skillId: skill.skillId,
                    module: skill.module,
                    version: skill.version,
                    status: 'success',
                    attempts: attempt,
                    startedAt: startedAt.toISOString(),
                    endedAt: endedAt.toISOString(),
                    durationMs: endedAt.getTime() - startedAt.getTime(),
                    timeoutMs: skill.executionPolicy.timeoutMs,
                    input: this.createSkillInput(command, skill.skillId, skill.module),
                    output
                };
            }
            catch (error) {
                lastError = error instanceof Error ? error.message : 'execution error';
                if (attempt < attempts) {
                    const elapsed = Date.now() - attemptStartedAt;
                    const remainingBudget = Math.max(skill.executionPolicy.timeoutMs - elapsed, 0);
                    const waitMs = Math.min(skill.executionPolicy.retry.backoffMs, remainingBudget);
                    if (waitMs > 0) {
                        await this.sleep(waitMs);
                    }
                }
            }
        }
        const endedAt = new Date();
        return {
            skillId: skill.skillId,
            module: skill.module,
            version: skill.version,
            status: 'failed',
            attempts,
            startedAt: startedAt.toISOString(),
            endedAt: endedAt.toISOString(),
            durationMs: endedAt.getTime() - startedAt.getTime(),
            timeoutMs: skill.executionPolicy.timeoutMs,
            input: this.createSkillInput(command, skill.skillId, skill.module),
            error: lastError
        };
    }
    async executeSkillWithTimeout(skill, command) {
        return await Promise.race([
            this.executeSkill(skill, command),
            new Promise((_, reject) => {
                const timer = setTimeout(() => {
                    clearTimeout(timer);
                    reject(new Error(`timeout ${skill.executionPolicy.timeoutMs}ms`));
                }, skill.executionPolicy.timeoutMs);
            })
        ]);
    }
    async executeSkill(skill, command) {
        switch (skill.module) {
            case 'guard': {
                const result = this.guard.validateCommand(command);
                if (!result.valid) {
                    throw new Error(result.reason ?? 'guard validation failed');
                }
                return { valid: true };
            }
            case 'memory': {
                this.memory.append(command.sessionId, `skill memory append: ${skill.skillId}`);
                return { accepted: true };
            }
            case 'policy': {
                if (command.riskLevel === 'high') {
                    throw new Error('policy check failed: high risk requires confirmation');
                }
                return { allow: true };
            }
            case 'executor': {
                return await this.dispatchToAuboMcp(command);
            }
            case 'planner': {
                return { summary: `plan for ${command.action} -> ${command.target}` };
            }
            case 'runtime': {
                return { accepted: true, taskState: 'running' };
            }
            case 'model': {
                return { parsed: true, action: command.action, target: command.target };
            }
            case 'realtime': {
                return { delivered: true, channel: `session:${command.sessionId}` };
            }
            case 'audit': {
                return { recorded: true, category: 'execution' };
            }
            case 'gateway': {
                return { connected: true, connectionId: command.connectionId };
            }
            default:
                return { noop: true };
        }
    }
    async dispatchToAuboMcp(command) {
        const toolCall = this.auboMcp.inferToolCallFromCommand(command);
        const policyCheck = this.auboMcp.validateToolCall(toolCall.toolName, toolCall.args);
        if (!policyCheck.ok) {
            throw new Error(`tool whitelist blocked: ${policyCheck.errors.join('; ')}`);
        }
        if (policyCheck.requireConfirmation && !this.isHighRiskConfirmed(command)) {
            throw new Error(contracts_1.ErrorCode.POLICY_CONFIRM_REQUIRED);
        }
        const result = await this.auboMcp.callToolRpc(toolCall.toolName, toolCall.args);
        if (!result.accepted) {
            throw new Error(result.message || 'aubo mcp rejected command');
        }
        return {
            accepted: true,
            route: `${command.ipcIp}:${command.targetType}`,
            action: command.action,
            target: command.target,
            toolName: toolCall.toolName,
            toolArgs: toolCall.args,
            message: result.message,
            raw: result.raw
        };
    }
    isHighRiskConfirmed(command) {
        const value = command.params.highRiskConfirmed;
        if (typeof value === 'boolean') {
            return value;
        }
        if (typeof value === 'string') {
            return value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === 'yes';
        }
        if (typeof value === 'number') {
            return value === 1;
        }
        return false;
    }
    async reportSkillStart(sessionId, taskId, node, command) {
        const event = {
            eventId: `evt_${Math.random().toString(36).slice(2, 10)}`,
            traceId: `trace_${Math.random().toString(36).slice(2, 10)}`,
            type: 'runtime.skill.started',
            taskId,
            sessionId,
            timestamp: new Date().toISOString(),
            payload: {
                skillId: node.skillId,
                module: node.module,
                version: node.version,
                timeoutMs: node.timeoutMs,
                maxAttempts: node.maxAttempts,
                input: this.createSkillInput(command, node.skillId, node.module)
            }
        };
        await this.publishRealtimeEvent(event, {
            action: `skill.started.${node.skillId}`,
            result: 'allow',
            message: `${node.skillId} started`
        });
    }
    async reportSkillTrace(sessionId, taskId, trace) {
        const typeMap = {
            success: 'runtime.skill.completed',
            failed: 'runtime.skill.failed',
            skipped: 'runtime.skill.skipped'
        };
        const event = {
            eventId: `evt_${Math.random().toString(36).slice(2, 10)}`,
            traceId: `trace_${Math.random().toString(36).slice(2, 10)}`,
            type: typeMap[trace.status],
            taskId,
            sessionId,
            timestamp: new Date().toISOString(),
            payload: trace
        };
        const auditResult = trace.status === 'success' ? 'allow' : 'error';
        await this.publishRealtimeEvent(event, {
            action: `skill.${trace.status}.${trace.skillId}`,
            result: auditResult,
            message: trace.error ?? `${trace.skillId} ${trace.status}`
        });
    }
    async publishRealtimeEvent(event, audit) {
        try {
            await fetch('http://localhost:3000/api/v1/realtime/events/publish', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ event, audit })
            });
        }
        catch {
            this.memory.append(event.sessionId ?? 'unknown', `realtime publish failed: ${event.type}`);
        }
    }
    async sleep(ms) {
        await new Promise((resolve) => {
            setTimeout(resolve, ms);
        });
    }
    createSkillInput(command, skillId, module) {
        return {
            skillId,
            module,
            sessionId: command.sessionId,
            targetType: command.targetType,
            action: command.action,
            target: command.target,
            params: command.params
        };
    }
    stopTask(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            return undefined;
        }
        task.status = 'stopped';
        task.updatedAt = new Date().toISOString();
        this.memory.append(task.sessionId, `task stopped: ${task.taskId}`);
        this.tasks.set(taskId, task);
        return task;
    }
    getTask(taskId) {
        return this.tasks.get(taskId);
    }
};
exports.RuntimeService = RuntimeService;
exports.RuntimeService = RuntimeService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [guard_service_1.GuardService,
        memory_service_1.MemoryService,
        registry_service_1.RegistryService,
        aubo_mcp_service_1.AuboMcpService])
], RuntimeService);
//# sourceMappingURL=runtime.service.js.map