/**
 * DeepAgentsAdapter -- Orchestrator adapter for deepagents (LangChain-ai / Harrison Chase).
 *
 * Architecture: deepagents returns a compiled LangGraph graph as its execution
 * substrate. This adapter extends LangGraphAdapter and overrides the three
 * additional surfaces that deepagents adds on top of LangGraph:
 *
 *   1. Sub-agent spawning via the `task` tool
 *      - When a parent agent spawns a sub-agent, the sub-agent MUST inherit
 *        the parent's pack bindings (unless the pack explicitly forbids delegation).
 *      - Emits canonical event: `agent.subagent.spawned` (mapped to the closest
 *        existing canonical event: orchestrator:workflow-start with subagent flag).
 *
 *   2. Todo-list middleware
 *      - The planning tool produces a structured todo list that becomes part of
 *        the cycle's context. Each todo item is classified for data-class tags
 *        AT EMIT TIME, not at consumption time (fail-forward classification rule).
 *
 *   3. Context summarization layer
 *      - Summaries can contain distilled PHI/PII even when the underlying
 *        messages were redacted. The ingress classifier is applied to every
 *        summary output before it propagates.
 *
 * Everything else routes to the parent LangGraphAdapter unchanged.
 *
 * Integration:
 *   const adapter = new DeepAgentsAdapter(deepAgentsCfg, langGraphCfg);
 *   adapter.initialize(orchCfg);
 *   const saver = adapter.toCheckpointSaver();
 *   const graph = agent.compile({ checkpointer: saver });
 *
 *   // Additional deepagents-specific hooks:
 *   await adapter.subAgentSpawn(parentAgentId, childAgentId, spawnContext);
 *   await adapter.processTodoList(agentId, todoItems);
 *   await adapter.processSummary(agentId, summaryText, summaryContext);
 *
 * Pack inheritance rules for sub-agent spawning:
 *   - By default, child agent inherits ALL pack bindings from the parent.
 *   - If a pack carries a 'no-delegation' flag, inheritance of THAT pack is
 *     refused and the spawn is denied. Other packs may still propagate.
 *   - An empty resulting binding after filtering is an error (UnboundDataError).
 *
 * No live deepagents SDK installed. All framework surfaces are shims.
 * DEFERRED-DEEPAGENTS-V1-API: Verify exact hook surface when deepagents
 * stabilises its Python/JS API (beta as of 2026-04-23).
 *
 * Spec: P3-SUB-ADAPT-06
 * @connexum/ai-governance
 */
import * as crypto from 'crypto';
import { LangGraphAdapter } from './langgraph.js';
import { OrchestratorError } from './index.js';
// ---------------------------------------------------------------------------
// DeepAgentsAdapter
// ---------------------------------------------------------------------------
export class DeepAgentsAdapter extends LangGraphAdapter {
    // deepagents is a thin extension layer on top of LangGraph.
    // The adapterId and orchestratorId are inherited from LangGraphAdapter
    // ('langgraph') because the underlying graph execution substrate is LangGraph.
    // The deepagents-specific hook surfaces (subAgentSpawn, processTodoList,
    // processSummary) identify themselves via the `layer: 'deepagents-*'` field
    // in canonical event payloads, which is the correct granularity for audit
    // queries that need to distinguish deepagents events from bare LangGraph events.
    //
    // DEFERRED-DEEPAGENTS-V1-API: If deepagents ships its own distinct registration
    // surface (separate from LangGraph), promote these to their own literal IDs.
    deepAgentsConfig;
    // ---------------------------------------------------------------------------
    // H9c: Strip caller-supplied org fields from agentId strings
    // ---------------------------------------------------------------------------
    /**
     * Strip any org-field prefix from a caller-supplied agentId string.
     *
     * Deepagents agentIds may include an optional org prefix of the form
     * "org:<orgName>/<localId>" or "org:<orgName>:<localId>".
     * Because callers are untrusted, any org field embedded in the agentId
     * MUST be ignored for authorization/scoping purposes. Only the
     * JWT-derived orgId from the adapter's config is authoritative.
     *
     * This method returns only the local agent identifier (the part after
     * the final "/" or ":" separator if an "org:" prefix is present).
     * If no org prefix is detected, the original string is returned unchanged.
     *
     * Examples:
     *   "org:attacker/agent-123"  -> "agent-123"
     *   "org:attacker:agent-123"  -> "agent-123"
     *   "agent-123"               -> "agent-123"
     *   "org:legit/team/agent-1"  -> "agent-1"
     */
    static _extractAgentLocalId(agentId) {
        // Detect org-prefixed form: "org:<anything>/<localId>" or "org:<anything>:<localId>"
        const orgPrefixMatch = /^org:[^/:]+(\/|:)(.+)$/.exec(agentId);
        if (orgPrefixMatch) {
            // Recurse to handle nested org prefixes
            return DeepAgentsAdapter._extractAgentLocalId(orgPrefixMatch[2]);
        }
        return agentId;
    }
    constructor(deepAgentsCfg, langGraphCfg) {
        super(langGraphCfg);
        this.deepAgentsConfig = {
            noDelegationPacks: [],
            classifyTodoItems: true,
            classifySummaries: true,
            ...deepAgentsCfg,
        };
    }
    // ---------------------------------------------------------------------------
    // subAgentSpawn -- deepagents `task` tool governance hook
    // ---------------------------------------------------------------------------
    /**
     * Called when a parent agent spawns a sub-agent via the `task` tool.
     *
     * Pack inheritance rules:
     *   1. Compute effective binding = parent binding INTERSECT requested child binding
     *      (child cannot escalate privilege above parent)
     *   2. Remove any packs in noDelegationPacks (pack-level delegation prohibition)
     *   3. If the effective binding is empty after filtering, deny (UnboundDataError)
     *   4. Emit canonical event with subagentSpawn=true marker
     *
     * Returns the EFFECTIVE pack binding the child should operate under.
     * The canonical event name is orchestrator:workflow-start (closest match) with
     * a `subagentSpawn: true` flag in the payload so compliance queries can filter.
     *
     * Throws OrchestratorError or BedrockUnboundDataError (as OrchestratorError) if denied.
     */
    async subAgentSpawn(parentAgentId, childAgentId, spawnContext) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        // H9c: Strip any org-field prefix from caller-supplied agentId strings.
        // Callers are untrusted. An attacker may embed "org:<foreignOrg>/<id>" to
        // inject a cross-tenant orgId into the audit trail. We normalize to the
        // local identifier only, and scope all canonical events with the JWT-derived
        // orgId from the adapter config (which is server-validated at initialization).
        const safeParentAgentId = DeepAgentsAdapter._extractAgentLocalId(parentAgentId);
        const safeChildAgentId = DeepAgentsAdapter._extractAgentLocalId(childAgentId);
        // JWT-derived orgId: authoritative for all events emitted by this adapter.
        // If not set (e.g., single-tenant deployments), orgId is omitted from events.
        const jwtOrgId = this.config.jwtOrgId;
        const parentBinding = spawnContext.parentPackBinding;
        const requestedChild = spawnContext.requestedChildPackBinding;
        // Step 1: Child cannot escalate above parent (intersection)
        const inheritedBinding = requestedChild.filter((p) => parentBinding.includes(p));
        // Step 2: Remove no-delegation packs
        const noDelegation = new Set(spawnContext.noDelegationPacks ?? this.deepAgentsConfig.noDelegationPacks ?? []);
        const effectiveBinding = inheritedBinding.filter((p) => !noDelegation.has(p));
        // Log any filtered packs
        const filteredPacks = inheritedBinding.filter((p) => noDelegation.has(p));
        if (filteredPacks.length > 0) {
            this.emitCanonical('orchestrator:delegation-denied', {
                parentAgentId: safeParentAgentId,
                childAgentId: safeChildAgentId,
                filteredPacks,
                ...(jwtOrgId !== undefined ? { orgId: jwtOrgId } : {}),
                reason: `Sub-agent spawn filtered out no-delegation packs: [${filteredPacks.join(', ')}]. ` +
                    'These packs explicitly prohibit delegation to sub-agents.',
            });
        }
        // Step 3: Fail closed if effective binding is empty
        if (effectiveBinding.length === 0) {
            const reason = `Sub-agent spawn refused: effective pack binding is empty after filtering. ` +
                `Parent binding: [${parentBinding.join(', ')}]. ` +
                `Requested child: [${requestedChild.join(', ')}]. ` +
                `No-delegation packs: [${[...noDelegation].join(', ')}]. ` +
                'Every agent MUST carry packBinding before any action fires.';
            this.emitCanonical('orchestrator:task-denied', {
                parentAgentId: safeParentAgentId,
                childAgentId: safeChildAgentId,
                ...(jwtOrgId !== undefined ? { orgId: jwtOrgId } : {}),
                reason,
                layer: 'deepagents-subagent-spawn',
            });
            throw new OrchestratorError({
                message: reason,
                code: 'DEEPAGENTS_EMPTY_PACK_BINDING_AFTER_INHERITANCE',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // Step 4: Emit canonical spawn event (subagentSpawn=true for compliance queries)
        // orgId is ALWAYS the JWT-derived value from adapter config, never the raw
        // caller-supplied agentId string (H9c cross-tenant spoof protection).
        this.emitCanonical('orchestrator:workflow-start', {
            parentAgentId: safeParentAgentId,
            childAgentId: safeChildAgentId,
            subagentSpawn: true,
            effectivePackBinding: effectiveBinding,
            parentPackBinding: parentBinding,
            requestedChildPackBinding: requestedChild,
            filteredPacks,
            childModel: spawnContext.childModel ?? null,
            layer: 'deepagents-task-tool',
            ...(jwtOrgId !== undefined ? { orgId: jwtOrgId } : {}),
            auditId,
        });
        return { effectivePackBinding: effectiveBinding, auditId };
    }
    // ---------------------------------------------------------------------------
    // processTodoList -- todo-list middleware governance hook
    // ---------------------------------------------------------------------------
    /**
     * Called when the deepagents planning tool produces a structured todo list.
     * Classifies each todo item for data-class tags AT EMIT TIME.
     *
     * Classification happens before the todo list enters the cycle's context.
     * Any item containing PHI/PAN is tagged; the full classified list is emitted
     * as a batch in a single canonical audit event.
     *
     * Returns the classified items (with dataClass tags added).
     */
    async processTodoList(agentId, items) {
        this.assertInitialized();
        const classified = items.map((item) => {
            const dataClass = this.classifyTodoItem(item);
            return { ...item, dataClass };
        });
        const sensitiveItems = classified.filter((c) => c.dataClass === 'SENSITIVE' || c.dataClass === 'PAN');
        // Emit a batch audit event for the full todo list
        this.emitCanonical('orchestrator:plan-proposed', {
            agentId,
            todoItemCount: classified.length,
            sensitiveItemCount: sensitiveItems.length,
            classifiedItems: classified.map((c) => ({
                itemId: c.itemId,
                dataClass: c.dataClass,
                isSubAgentTask: c.isSubAgentTask ?? false,
            })),
            layer: 'deepagents-todo-middleware',
        });
        // If any sensitive items exist, emit a compliance review event
        if (sensitiveItems.length > 0) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                reason: `deepagents todo-list contains ${sensitiveItems.length} item(s) with PHI/PAN. ` +
                    'Data-class tags applied at emit time per fail-forward classification rule. ' +
                    'Surfaced to compliance review.',
                sensitiveItemIds: sensitiveItems.map((i) => i.itemId),
                layer: 'todo-classifier',
            });
        }
        return classified;
    }
    // ---------------------------------------------------------------------------
    // processSummary -- context summarization layer governance hook
    // ---------------------------------------------------------------------------
    /**
     * Called when the deepagents context summarization layer produces a summary.
     * Applies the ingress PHI/PAN classifier to the summary text.
     *
     * Summary outputs can contain DISTILLED PHI even when underlying messages
     * were individually redacted (the summarization process can reassemble
     * identifying information from fragments). This hook catches that risk.
     *
     * If PHI/PAN is detected, emits a compliance review event. Does not block
     * (post-hoc scan) but surfaces to the compliance officer queue.
     *
     * Returns: whether PHI/PAN was detected in the summary.
     */
    async processSummary(agentId, summaryText, summaryContext) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (this.deepAgentsConfig.classifySummaries === false) {
            return { hasPhiContent: false, auditId };
        }
        // W1: use PackDrivenClassifier instead of removed containsPhiPattern method
        const { getGlobalClassifier: _getGC2 } = require('../classification/pack-driven-classifier');
        const _pdcSummary = _getGC2();
        const hasPhiContent = _pdcSummary.registrySize > 0
            ? _pdcSummary.classify(summaryText).hasMatch
            : /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b|\b(patient_id|mrn|dob|date_of_birth|diagnosis|ssn|npi)\s*[:=]/i.test(summaryText);
        this.emitCanonical('orchestrator:plan-step-end', {
            agentId,
            cycleId: summaryContext?.cycleId ?? null,
            originalMessageCount: summaryContext?.originalMessageCount ?? null,
            originalHadRedactedPhi: summaryContext?.originalHadRedactedPhi ?? null,
            summaryHasPhi: hasPhiContent,
            layer: 'deepagents-summary-classifier',
            auditId,
        });
        if (hasPhiContent) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                reason: 'deepagents context summary contains possible PHI/PAN. ' +
                    'Distilled PHI can appear in summaries even when underlying messages were redacted. ' +
                    'Surfaced to compliance review queue.',
                cycleId: summaryContext?.cycleId ?? null,
                layer: 'summary-phi-classifier',
            });
        }
        return { hasPhiContent, auditId };
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    /**
     * Classify a single todo item for data-class tags.
     * Returns the most severe class found (PHI > PAN > PII > CLEAN).
     */
    classifyTodoItem(item) {
        const text = [
            item.description,
            JSON.stringify(item.metadata ?? {}),
        ].join(' ');
        // PAN structural check (credit card) -- independent of pack categories
        if (/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,4}\b/.test(text)) {
            return 'PAN';
        }
        // Pack-driven sensitive category check (W1: no hardcoded 'PHI' string).
        // Consults the global PackDrivenClassifier which is loaded from active
        // pack declarations at boot time.
        const { getGlobalClassifier } = require('../classification/pack-driven-classifier');
        const classifier = getGlobalClassifier();
        if (classifier.registrySize > 0) {
            const result = classifier.classify(text);
            if (result.hasMatch)
                return 'SENSITIVE';
        }
        else {
            // Fallback heuristic when no packs are loaded (test / uninitialized context).
            // Uses structural patterns only (no category string literals).
            if (/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/.test(text))
                return 'SENSITIVE';
            if (/\b(patient_id|mrn|dob|date_of_birth|diagnosis|ssn|npi)\s*[:=]/i.test(text))
                return 'SENSITIVE';
        }
        // Generic PII check (email pattern)
        if (/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/.test(text)) {
            return 'PII';
        }
        return 'CLEAN';
    }
    // ---------------------------------------------------------------------------
    // Capabilities -- override to reflect deepagents additions
    // ---------------------------------------------------------------------------
    getOrchestratorCapabilities() {
        return {
            ...super.getOrchestratorCapabilities(),
            // deepagents adds these on top of LangGraph:
            subAgentSpawning: true,
            todoListMiddleware: true,
            contextSummarization: true,
        };
    }
}
//# sourceMappingURL=deepagents.js.map