"use strict";
/**
 * LlamaIndexAdapter -- Orchestrator adapter for LlamaIndex Agents.
 *
 * Hook surface: LlamaIndex exposes FunctionAgent, ReActAgent, and CodeActAgent
 * as primary agent entry points. Our adapter is the TypeScript governance-side
 * consumer of normalised event payloads produced at each stage of the LlamaIndex
 * pipeline. Customers wire the adapter at the governance boundary:
 *
 *   const adapter = new LlamaIndexAdapter(cfg);
 *   adapter.initialize(orchCfg);
 *
 *   // At agent spawn:
 *   await adapter.agentStart(sessionId, agentType, contextSnapshot);
 *
 *   // Before a tool/function call:
 *   await adapter.toolCall(sessionId, toolName, args);
 *
 *   // After a tool result returns:
 *   await adapter.toolResult(sessionId, toolName, result);
 *
 *   // Before an LLM call:
 *   await adapter.llmCall(sessionId, model, messages);
 *
 *   // After a retrieved document chunk is classified:
 *   await adapter.documentRetrieval(sessionId, chunks);
 *
 *   // After the response synthesis stage:
 *   await adapter.responseSynthesis(sessionId, synthesizedText, sourceChunkIds);
 *
 *   // When the agent terminates:
 *   await adapter.agentEnd(sessionId, outcome);
 *
 * LlamaIndex-specific considerations:
 *   - Primary runtime is Python; our adapter is the TypeScript governance-side
 *     consumer of normalised event payloads (same pattern as ADK).
 *   - DocumentStore and VectorStore retrieval is a first-class ingress surface.
 *     Every retrieved chunk is classified for PHI before the agent sees it.
 *     Common healthcare document classes: medical records, lab PDFs, clinical notes.
 *   - ReAct trace format (Thought -> Action -> Observation) is surfaced in canonical
 *     audit events as a structured reactTrace field.
 *   - Response synthesis stage (where LlamaIndex assembles the final answer from
 *     retrieved chunks) is a post-retrieval ingress stage: the synthesised response
 *     is classified before return to the caller.
 *
 * Healthcare document classes handled at ingress:
 *   MedicalRecord, LabReport, ClinicalNote, PatientChart, PathologyReport,
 *   RadiologyReport, PrescriptionRecord, InsuranceClaim
 *
 * No live LlamaIndex Python SDK installed. All framework surfaces are shims.
 * DEFERRED-LLAMAINDEX-V1-API: Verify exact hook surface when LlamaIndex
 * stabilises its TypeScript / event-bus API.
 *
 * Pentest gate:
 *   PT-ADAPT-LLAMAINDEX-1: retrieved chunk with PHI and no HIPAA binding is refused.
 *   PT-ADAPT-LLAMAINDEX-2: response synthesis reveals distilled PHI that was not
 *     present in any single chunk.
 *   PT-ADAPT-LLAMAINDEX-3: ReAct Observation leaking to external tool call is caught
 *     by egress guard.
 *
 * Spec: P3-SUB-ADAPT-04
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.LlamaIndexAdapter = exports.HEALTHCARE_DOC_KEYWORDS = exports.LlamaIndexReactObservationLeakError = exports.LlamaIndexSynthesisPhiError = exports.LlamaIndexPhiRetrievalError = exports.LlamaIndexUnboundDataError = void 0;
const crypto = __importStar(require("crypto"));
const base_1 = require("./base");
const index_1 = require("./index");
const index_2 = require("../adapters/index");
const index_3 = require("../errors/index");
Object.defineProperty(exports, "LlamaIndexUnboundDataError", { enumerable: true, get: function () { return index_3.LlamaIndexUnboundDataError; } });
/**
 * Thrown when a retrieved document chunk contains PHI and HIPAA enforcement
 * is active with no BAA-covered provider on file.
 */
class LlamaIndexPhiRetrievalError extends Error {
    sessionId;
    chunkId;
    documentClass;
    constructor(sessionId, chunkId, documentClass) {
        super(`LlamaIndex session "${sessionId}" retrieved chunk "${chunkId}" (class: ${documentClass}) ` +
            'containing PHI but HIPAA pack binding is active with no BAA-covered provider. ' +
            'Blocked at document-retrieval ingress gate.');
        this.name = 'LlamaIndexPhiRetrievalError';
        this.sessionId = sessionId;
        this.chunkId = chunkId;
        this.documentClass = documentClass;
    }
}
exports.LlamaIndexPhiRetrievalError = LlamaIndexPhiRetrievalError;
/**
 * Thrown when the response synthesis stage produces output containing PHI
 * that was not detectable in any single source chunk (distillation risk).
 */
class LlamaIndexSynthesisPhiError extends Error {
    sessionId;
    constructor(sessionId) {
        super(`LlamaIndex session "${sessionId}" response synthesis produced output containing PHI ` +
            'that was not present in any single source chunk. ' +
            'Distilled PHI detected at synthesis-stage classifier. Blocked.');
        this.name = 'LlamaIndexSynthesisPhiError';
        this.sessionId = sessionId;
    }
}
exports.LlamaIndexSynthesisPhiError = LlamaIndexSynthesisPhiError;
/**
 * Thrown when a ReAct Observation contains sensitive data that is about to
 * be passed to an external tool call (observation egress leak).
 */
class LlamaIndexReactObservationLeakError extends Error {
    sessionId;
    toolName;
    constructor(sessionId, toolName) {
        super(`LlamaIndex session "${sessionId}" ReAct Observation contains PHI/PAN that would ` +
            `be forwarded to external tool "${toolName}". Blocked at egress guard.`);
        this.name = 'LlamaIndexReactObservationLeakError';
        this.sessionId = sessionId;
        this.toolName = toolName;
    }
}
exports.LlamaIndexReactObservationLeakError = LlamaIndexReactObservationLeakError;
/** Keywords that signal a document is in a protected healthcare class. */
exports.HEALTHCARE_DOC_KEYWORDS = new Map([
    ['MedicalRecord', ['medical record', 'patient record', 'health record', 'emr', 'ehr']],
    ['LabReport', ['lab report', 'laboratory', 'hba1c', 'cbc', 'urinalysis', 'blood panel']],
    ['ClinicalNote', ['clinical note', 'soap note', 'progress note', 'physician note', 'nursing note']],
    ['PatientChart', ['patient chart', 'chart note', 'encounter note']],
    ['PathologyReport', ['pathology', 'biopsy', 'histology', 'cytology']],
    ['RadiologyReport', ['radiology', 'x-ray', 'xray', 'mri', 'ct scan', 'ultrasound', 'mammography']],
    ['PrescriptionRecord', ['prescription', 'rx', 'medication order', 'drug order']],
    ['InsuranceClaim', ['insurance claim', 'eob', 'explanation of benefits', 'claim form', 'cms-1500']],
]);
// ---------------------------------------------------------------------------
// LlamaIndexAdapter
// ---------------------------------------------------------------------------
class LlamaIndexAdapter extends base_1.BaseOrchestratorAdapter {
    adapterId = 'llamaindex';
    orchestratorId = 'llamaindex';
    llamaConfig;
    /** Per-session tool call counters for runaway detection. */
    toolCallCounters = new Map();
    /** Active sessions: sessionId -> contextSnapshot */
    activeSessions = new Map();
    /**
     * Per-session ReAct trace buffer.
     * Surfaces Thought -> Action -> Observation trace in audit events.
     */
    reactTraces = new Map();
    /**
     * Per-session retrieval history (chunk IDs only, not content).
     * Used by the synthesis-stage classifier to detect distillation risk.
     */
    retrievalHistory = new Map();
    constructor(config) {
        super();
        this.llamaConfig = {
            blockPhiRetrieval: true,
            blockPhiSynthesis: true,
            enforcePackBinding: true,
            ...config,
        };
    }
    // ---------------------------------------------------------------------------
    // agentStart -- gate at LlamaIndex agent spawn
    // ---------------------------------------------------------------------------
    /**
     * Called when a LlamaIndex agent session starts.
     *   1. Session registration check
     *   2. Pack-binding resolver
     *   3. Provider compliance check on model
     *   4. Document-store / vector-store ingress surface registration
     *
     * Throws LlamaIndexUnboundDataError or OrchestratorError if denied.
     */
    async agentStart(sessionId, agentType, contextSnapshot) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        // -- Circuit breaker -------------------------------------------------------
        if (this.isCircuitOpen()) {
            const reason = 'Circuit breaker is open; LlamaIndex agent session denied';
            this.emitCanonical('orchestrator:task-denied', { sessionId, agentType, reason });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_CIRCUIT_OPEN',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Session registration check -------------------------------------------
        const isRegistered = this.llamaConfig.registeredSessions.length === 0
            ? false
            : this.llamaConfig.registeredSessions.some((prefix) => sessionId.startsWith(prefix));
        if (!isRegistered) {
            const reason = `LlamaIndex session "${sessionId}" is not registered`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, agentType, reason });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_UNREGISTERED_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Pack-binding resolver -------------------------------------------------
        if (this.llamaConfig.enforcePackBinding !== false &&
            (!contextSnapshot.packBinding || contextSnapshot.packBinding.length === 0)) {
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                agentType,
                reason: 'Missing pack binding at LlamaIndex agentStart',
            });
            this.recordFailure();
            throw new index_3.LlamaIndexUnboundDataError(sessionId, 'agentStart');
        }
        // -- Provider / model compliance ------------------------------------------
        if (!this.llamaConfig.approvedModels.includes(contextSnapshot.model)) {
            const reason = `LlamaIndex model "${contextSnapshot.model}" is not on the approved model list ` +
                `for pack binding [${contextSnapshot.packBinding.join(', ')}].`;
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                agentType,
                model: contextSnapshot.model,
                approvedModels: this.llamaConfig.approvedModels,
                reason,
            });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_UNAPPROVED_MODEL',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Register session state -----------------------------------------------
        this.activeSessions.set(sessionId, contextSnapshot);
        this.toolCallCounters.set(sessionId, 0);
        if (agentType === 'ReActAgent') {
            this.reactTraces.set(sessionId, []);
        }
        this.retrievalHistory.set(sessionId, new Set());
        // -- Emit canonical spawn event -------------------------------------------
        this.emitCanonical('orchestrator:workflow-start', {
            sessionId,
            agentType,
            model: contextSnapshot.model,
            packBinding: contextSnapshot.packBinding,
            hasDocumentStore: contextSnapshot.hasDocumentStore,
            hasVectorStore: contextSnapshot.hasVectorStore,
            providerId: contextSnapshot.providerId ?? null,
            auditId,
        });
        this.recordSuccess();
        return {
            type: 'ALLOW',
            reason: `LlamaIndex session "${sessionId}" (${agentType}) approved (pack: ${contextSnapshot.packBinding.join(',')})`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // documentRetrieval -- classify chunks at VectorStore/DocumentStore ingress
    // ---------------------------------------------------------------------------
    /**
     * Called when LlamaIndex retrieves document chunks from its store.
     * This is the primary PHI ingress surface for healthcare workflows.
     *
     * Each chunk is:
     *   1. Classified for healthcare document class
     *   2. Scanned for PHI patterns
     *   3. Blocked if HIPAA pack active + PHI present + blockPhiRetrieval=true
     *
     * Returns classified chunks (with hasPhi + documentClass tags).
     *
     * Throws LlamaIndexPhiRetrievalError for the first PHI chunk found
     * when HIPAA is active and blockPhiRetrieval=true.
     */
    async documentRetrieval(sessionId, chunks) {
        this.assertInitialized();
        const snapshot = this.activeSessions.get(sessionId);
        if (!snapshot) {
            const reason = `LlamaIndex session "${sessionId}" called documentRetrieval without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, reason });
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const hipaaActive = this.hasHipaaBinding(snapshot.packBinding);
        const history = this.retrievalHistory.get(sessionId) ?? new Set();
        const classified = chunks.map((chunk) => {
            const documentClass = chunk.documentClass ?? this.classifyDocumentClass(chunk.content);
            const hasPhi = this.containsPhiPattern(chunk.content);
            return { ...chunk, documentClass, hasPhi };
        });
        // Record chunk IDs in session history (for synthesis distillation check)
        for (const c of classified) {
            history.add(c.chunkId);
        }
        this.retrievalHistory.set(sessionId, history);
        // Count PHI chunks
        const phiChunks = classified.filter((c) => c.hasPhi);
        this.emitCanonical('orchestrator:plan-step-begin', {
            sessionId,
            stage: 'document-retrieval',
            totalChunks: classified.length,
            phiChunkCount: phiChunks.length,
            hipaaActive,
            classifiedChunks: classified.map((c) => ({
                chunkId: c.chunkId,
                documentClass: c.documentClass,
                hasPhi: c.hasPhi,
            })),
            layer: 'llamaindex-retrieval',
        });
        // Block under HIPAA if PHI found and blocking is enabled
        if (hipaaActive && phiChunks.length > 0 && this.llamaConfig.blockPhiRetrieval !== false) {
            const firstPhiChunk = phiChunks[0];
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                stage: 'document-retrieval',
                phiChunkId: firstPhiChunk.chunkId,
                documentClass: firstPhiChunk.documentClass,
                reason: `Document retrieval blocked: chunk "${firstPhiChunk.chunkId}" ` +
                    `(class: ${firstPhiChunk.documentClass}) contains PHI under active HIPAA pack binding. ` +
                    'No BAA-covered provider confirmation. Fail closed at retrieval ingress.',
                layer: 'retrieval-phi-gate',
            });
            this.recordFailure();
            throw new LlamaIndexPhiRetrievalError(sessionId, firstPhiChunk.chunkId, firstPhiChunk.documentClass);
        }
        // Surface non-blocking PHI warnings even outside HIPAA
        if (!hipaaActive && phiChunks.length > 0) {
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                stage: 'document-retrieval',
                phiChunkCount: phiChunks.length,
                reason: `LlamaIndex retrieval: ${phiChunks.length} chunk(s) contain PHI. ` +
                    'No HIPAA binding active -- surfaced to compliance review.',
                layer: 'retrieval-phi-warn',
            });
        }
        return classified;
    }
    // ---------------------------------------------------------------------------
    // responseSynthesis -- post-retrieval synthesis classifier
    // ---------------------------------------------------------------------------
    /**
     * Called after LlamaIndex assembles the final answer from retrieved chunks.
     * The synthesised response is classified before it is returned to the caller.
     *
     * Risk: LlamaIndex's synthesis process can produce distilled PHI that was not
     * present in any single source chunk (e.g. correlating name + diagnosis across
     * multiple chunks). This hook catches that.
     *
     * Throws LlamaIndexSynthesisPhiError when PHI is detected and blockPhiSynthesis=true.
     */
    async responseSynthesis(sessionId, synthesizedText, sourceChunkIds) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const snapshot = this.activeSessions.get(sessionId);
        if (!snapshot) {
            const reason = `LlamaIndex session "${sessionId}" called responseSynthesis without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, reason });
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const hasPhi = this.containsPhiPattern(synthesizedText);
        const hipaaActive = this.hasHipaaBinding(snapshot.packBinding);
        this.emitCanonical('orchestrator:plan-step-end', {
            sessionId,
            stage: 'response-synthesis',
            sourceChunkCount: sourceChunkIds.length,
            synthesisHasPhi: hasPhi,
            hipaaActive,
            layer: 'llamaindex-synthesis',
            auditId,
        });
        if (hasPhi) {
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                stage: 'response-synthesis',
                reason: 'LlamaIndex synthesis stage produced output containing PHI. ' +
                    'Distilled PHI can appear in synthesised responses even when individual chunks ' +
                    'passed retrieval classification. Synthesis-stage classifier triggered.',
                hipaaActive,
                layer: 'synthesis-phi-classifier',
            });
            if (this.llamaConfig.blockPhiSynthesis !== false) {
                this.recordFailure();
                throw new LlamaIndexSynthesisPhiError(sessionId);
            }
        }
        return { hasPhi, auditId };
    }
    // ---------------------------------------------------------------------------
    // toolCall -- pre-tool gate with ReAct observation egress check
    // ---------------------------------------------------------------------------
    /**
     * Called before every LlamaIndex tool invocation.
     *   1. Active-session check
     *   2. Pack-binding check
     *   3. Runaway-session counter
     *   4. ReAct observation egress check (last Observation -> this tool call)
     *   5. Sensitive-tool REQUIRE_APPROVAL routing
     *   6. Exfiltration scan on sensitive argument keys
     */
    async toolCall(sessionId, toolName, args, reactStep) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        // -- Active-session check -------------------------------------------------
        if (!this.activeSessions.has(sessionId)) {
            const reason = `LlamaIndex session "${sessionId}" called toolCall without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, toolName, reason });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Pack-binding check ---------------------------------------------------
        const snapshot = this.activeSessions.get(sessionId);
        if (this.llamaConfig.enforcePackBinding !== false &&
            (!snapshot.packBinding || snapshot.packBinding.length === 0)) {
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                toolName,
                reason: 'Missing pack binding at LlamaIndex toolCall',
            });
            this.recordFailure();
            throw new index_3.LlamaIndexUnboundDataError(sessionId, `toolCall(${toolName})`);
        }
        // -- Runaway counter ------------------------------------------------------
        const callCount = (this.toolCallCounters.get(sessionId) ?? 0) + 1;
        this.toolCallCounters.set(sessionId, callCount);
        const maxCalls = this.llamaConfig.maxToolCallsPerSession;
        if (maxCalls !== undefined && callCount > maxCalls) {
            const reason = `LlamaIndex session "${sessionId}" exceeded maxToolCallsPerSession (${maxCalls}). ` +
                'Runaway agent suppressed.';
            this.emitCanonical('orchestrator:task-denied', { sessionId, toolName, callCount, reason });
            this.recordFailure();
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        // -- ReAct observation egress check (PT-ADAPT-LLAMAINDEX-3) ---------------
        if (reactStep?.observation && this.containsPhiPattern(reactStep.observation)) {
            const reason = `LlamaIndex ReAct Observation for step ${reactStep.stepIndex} contains PHI/PAN ` +
                `that would be forwarded to tool "${toolName}". Blocked at egress guard.`;
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                toolName,
                stepIndex: reactStep.stepIndex,
                reason,
                layer: 'react-observation-egress',
            });
            this.recordFailure();
            throw new LlamaIndexReactObservationLeakError(sessionId, toolName);
        }
        // -- Buffer ReAct step for audit trace ------------------------------------
        if (reactStep) {
            const trace = this.reactTraces.get(sessionId) ?? [];
            trace.push(reactStep);
            this.reactTraces.set(sessionId, trace);
        }
        // -- Exfiltration scan on sensitive argument keys -------------------------
        for (const key of this.llamaConfig.sensitiveArgKeys) {
            const value = args[key];
            if (typeof value === 'string' && this.containsPhiPattern(value)) {
                const reason = `Possible data exfiltration in LlamaIndex tool args for "${toolName}" ` +
                    `(key: "${key}"). Blocked at G5/G7 egress guard.`;
                this.emitCanonical('orchestrator:task-denied', { sessionId, toolName, exfilKey: key, reason });
                this.recordFailure();
                return { type: index_2.ActionDecisionType.DENY, reason, auditId };
            }
        }
        // -- Sensitive-tool approval routing --------------------------------------
        if (this.llamaConfig.sensitiveTools.includes(toolName)) {
            const tier = 'human-reviewer';
            const queueId = this.config.reviewerQueues[tier];
            if (!queueId) {
                const reason = `LlamaIndex tool "${toolName}" is sensitive but no human-reviewer queue binding exists. Fail closed.`;
                this.emitCanonical('orchestrator:task-denied', { sessionId, toolName, reason });
                this.recordFailure();
                return { type: index_2.ActionDecisionType.DENY, reason, auditId };
            }
            this.emitCanonical('orchestrator:reviewer-routed', {
                sessionId,
                toolName,
                tier,
                queueId,
                layer: 'llamaindex',
            });
            return {
                type: index_2.ActionDecisionType.REQUIRE_APPROVAL,
                reason: `LlamaIndex tool "${toolName}" requires human-reviewer approval`,
                auditId,
                metadata: { reviewerTier: tier, layer: 'llamaindex', sessionId, toolName },
            };
        }
        // -- Approve ---------------------------------------------------------------
        this.emitCanonical('orchestrator:task-approved', {
            sessionId,
            toolName,
            callCount,
            reactTrace: reactStep ? [reactStep] : [],
            layer: 'llamaindex',
        });
        return {
            type: index_2.ActionDecisionType.ALLOW,
            reason: `LlamaIndex tool "${toolName}" approved for session "${sessionId}"`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // toolResult -- post-tool audit + output classifier
    // ---------------------------------------------------------------------------
    async toolResult(sessionId, toolName, result) {
        this.assertInitialized();
        const outputString = typeof result.output === 'string'
            ? result.output
            : JSON.stringify(result.output ?? '');
        const hasSuspiciousOutput = this.containsPhiPattern(outputString);
        this.emitCanonical('orchestrator:plan-step-end', {
            sessionId,
            toolName,
            success: result.success,
            durationMs: result.durationMs ?? null,
            suspiciousOutput: hasSuspiciousOutput,
            layer: 'llamaindex',
        });
        if (hasSuspiciousOutput) {
            this.emitCanonical('orchestrator:task-denied', {
                sessionId,
                toolName,
                reason: 'LlamaIndex tool result contains possible PHI/PAN. ' +
                    'Surfaced to compliance review queue.',
                layer: 'post-tool-classifier',
            });
        }
    }
    // ---------------------------------------------------------------------------
    // llmCall -- pre-LLM gate
    // ---------------------------------------------------------------------------
    async llmCall(sessionId, model, messages) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (!this.activeSessions.has(sessionId)) {
            const reason = `LlamaIndex session "${sessionId}" called llmCall without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, model, reason });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const snapshot = this.activeSessions.get(sessionId);
        if (!this.llamaConfig.approvedModels.includes(model)) {
            const reason = `LlamaIndex model "${model}" is not on the approved provider list for ` +
                `pack binding [${snapshot.packBinding.join(', ')}].`;
            this.emitCanonical('orchestrator:task-denied', { sessionId, model, reason });
            this.recordFailure();
            throw new index_1.OrchestratorError({
                message: reason,
                code: 'LLAMAINDEX_UNAPPROVED_MODEL',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // Surface reactTrace in LLM call event (for ReActAgent sessions)
        const reactTrace = this.reactTraces.get(sessionId) ?? [];
        this.emitCanonical('orchestrator:plan-step-begin', {
            sessionId,
            model,
            messageCount: messages.length,
            packBinding: snapshot.packBinding,
            reactTraceLength: reactTrace.length,
            layer: 'llamaindex',
        });
        return {
            type: index_2.ActionDecisionType.ALLOW,
            reason: `LlamaIndex LLM call to "${model}" approved for session "${sessionId}"`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // agentEnd -- terminal audit event sealing
    // ---------------------------------------------------------------------------
    async agentEnd(sessionId, outcome) {
        this.assertInitialized();
        const snapshot = this.activeSessions.get(sessionId);
        const toolCallCount = this.toolCallCounters.get(sessionId) ?? 0;
        const reactTrace = this.reactTraces.get(sessionId) ?? [];
        const retrievedChunks = this.retrievalHistory.get(sessionId)?.size ?? 0;
        this.emitCanonical('orchestrator:workflow-complete', {
            sessionId,
            success: outcome.success,
            summary: outcome.summary ?? (outcome.success ? 'LlamaIndex session complete' : 'LlamaIndex session failed'),
            durationMs: outcome.durationMs ?? null,
            error: outcome.error ?? null,
            toolCallCount,
            reactTraceSteps: reactTrace.length,
            totalChunksRetrieved: outcome.totalChunksRetrieved ?? retrievedChunks,
            synthesisCalls: outcome.synthesisCalls ?? null,
            packBinding: snapshot?.packBinding ?? [],
            model: snapshot?.model ?? null,
        });
        this.activeSessions.delete(sessionId);
        this.toolCallCounters.delete(sessionId);
        this.reactTraces.delete(sessionId);
        this.retrievalHistory.delete(sessionId);
        if (outcome.success) {
            this.recordSuccess();
        }
        else {
            this.recordFailure();
        }
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    /** Check if HIPAA (or HITECH) pack is in the binding. */
    hasHipaaBinding(packBinding) {
        return packBinding.some((p) => p.toLowerCase().includes('hipaa') || p.toLowerCase().includes('hitech'));
    }
    /**
     * Classify the healthcare document class of a chunk based on content keywords.
     * Returns the first matching class or GenericDocument.
     */
    classifyDocumentClass(content) {
        const lower = content.toLowerCase();
        for (const [cls, keywords] of exports.HEALTHCARE_DOC_KEYWORDS) {
            if (keywords.some((kw) => lower.includes(kw))) {
                return cls;
            }
        }
        return 'GenericDocument';
    }
    /**
     * Heuristic PHI / PAN pattern check.
     * Mirrors the DataClassifier patterns at a lightweight level.
     */
    containsPhiPattern(text) {
        if (/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/.test(text))
            return true;
        if (/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,4}\b/.test(text))
            return true;
        if (/\b(patient_id|mrn|dob|date_of_birth|diagnosis|ssn|npi)\s*[:=]/i.test(text))
            return true;
        return false;
    }
    // ---------------------------------------------------------------------------
    // OrchestratorAdapter surface -- workflow lifecycle wrappers
    // ---------------------------------------------------------------------------
    async startWorkflow(ctx) {
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:workflow-start', {
            workflowId: ctx.workflowId,
            name: ctx.name,
            initiator: ctx.initiator.name,
        });
        return { type: 'ALLOW', reason: 'LlamaIndex workflow started', auditId };
    }
    async stepWorkflow(ctx) {
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:plan-step-begin', {
            workflowId: ctx.workflowId,
            stepId: ctx.stepId,
            stepIndex: ctx.stepIndex,
        });
        if (this.isCircuitOpen()) {
            this.emitCanonical('orchestrator:circuit-breaker-tripped', {
                workflowId: ctx.workflowId,
                stepId: ctx.stepId,
            });
            return { type: 'DENY', reason: 'Circuit breaker is open', auditId };
        }
        return { type: 'ALLOW', reason: 'Step permitted', auditId };
    }
    async completeWorkflow(ctx, result) {
        this.emitCanonical('orchestrator:workflow-complete', {
            workflowId: ctx.workflowId,
            success: result.success,
            summary: result.summary,
            durationMs: result.durationMs,
        });
    }
    async preTask(task) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (this.isCircuitOpen()) {
            const reason = 'Circuit breaker is open; task denied';
            this.emitCanonical('orchestrator:task-denied', { taskId: task.taskId, reason });
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:task-approved', {
            taskId: task.taskId,
            agentName: task.assignedAgent.name,
        });
        return { type: index_2.ActionDecisionType.ALLOW, reason: `Task "${task.taskId}" approved via LlamaIndexAdapter`, auditId };
    }
    async postTask(task, result) {
        this.emitCanonical('orchestrator:plan-step-end', { taskId: task.taskId, result });
    }
    async prePlan(plan) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:plan-proposed', { planId: plan.planId });
        try {
            this.validatePlanStructure(plan);
        }
        catch (err) {
            const reason = err instanceof Error ? err.message : String(err);
            this.emitCanonical('orchestrator:plan-denied', { planId: plan.planId, reason });
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:plan-approved', { planId: plan.planId });
        return { type: index_2.ActionDecisionType.ALLOW, reason: 'Plan approved', auditId };
    }
    async postPlan(plan, result) {
        this.emitCanonical('orchestrator:workflow-complete', { planId: plan.planId, result });
    }
    async preDelegation(d) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const restricted = d.permissions.filter((p) => this.llamaConfig.sensitiveTools.includes(p));
        if (restricted.length > 0) {
            const reason = `LlamaIndex delegation includes restricted permissions: ${restricted.join(', ')}`;
            this.emitCanonical('orchestrator:delegation-denied', {
                delegationId: d.delegationId,
                from: d.fromAgent.name,
                to: d.toAgent.name,
                restricted,
            });
            return { type: index_2.ActionDecisionType.DENY, reason, restrictedPermissions: restricted, auditId };
        }
        this.emitCanonical('orchestrator:delegation-approved', {
            delegationId: d.delegationId,
            from: d.fromAgent.name,
            to: d.toAgent.name,
        });
        return { type: index_2.ActionDecisionType.ALLOW, reason: 'Delegation approved', auditId };
    }
    async postDelegation(d, result) {
        this.emitCanonical('orchestrator:delegation-approved', { delegationId: d.delegationId, result });
    }
    async invokeRoleEdge(edge) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:role-edge-invoked', {
            edgeId: edge.edgeId,
            fromRole: edge.fromRole,
            toRole: edge.toRole,
        });
        return { type: 'ALLOW', reason: 'LlamaIndex role edge permitted', auditId };
    }
    translateError(raw) {
        if (raw instanceof index_1.OrchestratorError)
            return raw;
        const message = raw instanceof Error ? raw.message : String(raw);
        if (message.includes('LlamaIndexPhiRetrievalError') || message.includes('retrieval ingress gate')) {
            return new index_1.OrchestratorError({
                message: `LlamaIndex PHI retrieval violation: ${message}`,
                code: 'LLAMAINDEX_PHI_RETRIEVAL',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        if (message.includes('LlamaIndexSynthesisPhiError') || message.includes('synthesis-stage')) {
            return new index_1.OrchestratorError({
                message: `LlamaIndex synthesis PHI violation: ${message}`,
                code: 'LLAMAINDEX_SYNTHESIS_PHI',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        if (message.includes('LlamaIndexReactObservationLeakError') || message.includes('ReAct Observation')) {
            return new index_1.OrchestratorError({
                message: `LlamaIndex ReAct observation egress leak: ${message}`,
                code: 'LLAMAINDEX_REACT_OBSERVATION_LEAK',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        return new index_1.OrchestratorError({
            message,
            code: 'LLAMAINDEX_ERROR',
            orchestratorId: this.orchestratorId,
            adapterId: this.adapterId,
            raw,
        });
    }
    getOrchestratorCapabilities() {
        return {
            workflowGovernance: true,
            taskGovernance: true,
            planGovernance: true,
            delegationGovernance: true,
            roleEdgeGovernance: true,
            parallelExecution: false,
            nestedPlans: false,
            circuitBreaker: true,
        };
    }
}
exports.LlamaIndexAdapter = LlamaIndexAdapter;
//# sourceMappingURL=llamaindex.js.map