/**
 * OpenClawAdapter -- Orchestrator adapter for OpenClaw (Peter Steinberger, MIT).
 *
 * OpenClaw is a messaging-platform-first agent runner with 247k GitHub stars.
 * Architecture: persistent memory, multi-model support, primary delivery via
 * Telegram, WhatsApp, Slack, Discord, and Signal.
 *
 * License: MIT -- no IPR encumbrance. Pure community-inbound adapter.
 *
 * ARCHITECTURAL NOTE (honest limitation):
 *   OpenClaw is designed for personal / SMB use cases. Our adapter governs any
 *   OpenClaw deployment that opts in by wrapping OpenClaw's action + memory layer
 *   with our three-layer enforcement. The adapter does NOT turn OpenClaw into a
 *   multi-tenant compliance runtime -- that is architecturally incompatible with
 *   OpenClaw's single-user-first design. Regulated enterprise deployments should
 *   use this adapter AND a hardened runtime configuration.
 *
 *   DEFERRED-OPENCLAW-ENTERPRISE-HARDENING: For HIPAA-regulated OpenClaw
 *   deployments, a dedicated hardening guide is required (isolation, PHI
 *   minimisation, BAA confirmation with each messaging platform, persistent
 *   memory encryption at rest). Route to Vera for legal review before any
 *   production HIPAA deployment.
 *
 * Hook surface:
 *
 *   const adapter = new OpenClawAdapter(cfg);
 *   adapter.initialize(orchCfg);
 *
 *   // At agent session start:
 *   await adapter.agentStart(agentId, userId, platform, contextSnapshot);
 *
 *   // Before every action:
 *   await adapter.actionCall(agentId, actionName, args, platform);
 *
 *   // After every action result:
 *   await adapter.actionResult(agentId, actionName, result);
 *
 *   // Before writing to persistent memory:
 *   await adapter.memoryWrite(agentId, key, value, sessionId);
 *
 *   // Before reading from persistent memory:
 *   await adapter.memoryRead(agentId, key, sessionId);
 *
 *   // Before sending an outbound message to a messaging platform:
 *   await adapter.outboundMessage(agentId, platform, recipientId, content);
 *
 *   // Before an LLM call:
 *   await adapter.llmCall(agentId, model, messages);
 *
 *   // When the session ends:
 *   await adapter.agentEnd(agentId, outcome);
 *
 * Messaging platform surface:
 *   Every outbound message to Telegram / WhatsApp / Slack / Discord / Signal
 *   is an egress event. The nine-guard stack applies. Unsanctioned workspaces
 *   are blocked.
 *
 * Memory governance:
 *   OpenClaw's persistent memory across sessions means every memory write is
 *   an ingress event that must be classified. Every memory read is a potential
 *   PHI/PII leak surface. Cross-session memory reads pseudonymise protected
 *   identifiers when regulated packs are bound.
 *
 * Multi-model governance:
 *   Provider-compliance fires per-call, same pattern as other adapters.
 *
 * Pentest gate:
 *   PT-ADAPT-OPENCLAW-1: memory write containing PHI fails closed under HIPAA binding.
 *   PT-ADAPT-OPENCLAW-2: outbound message to unsanctioned Telegram / Slack workspace blocked.
 *   PT-ADAPT-OPENCLAW-3: cross-session memory retrieval pseudonymises protected identifiers.
 *
 * Spec: P3-SUB-ADAPT-07
 * @connexum/ai-governance
 */
import * as crypto from 'crypto';
import { BaseOrchestratorAdapter } from './base.js';
import { OrchestratorError, } from './index.js';
import { ActionDecisionType } from '../adapters/index.js';
import { OpenClawUnboundDataError } from '../errors/index.js';
// ---------------------------------------------------------------------------
// Error classes
// ---------------------------------------------------------------------------
/**
 * Thrown when an OpenClaw agent acts without a pack binding.
 * Re-exports canonical UnboundDataError as OpenClawUnboundDataError so
 * a single `instanceof UnboundDataError` at the server boundary matches
 * all adapter origins. (C2 fix, 2026-04-24)
 */
export { OpenClawUnboundDataError };
/**
 * Thrown when a memory write contains PHI and HIPAA binding is active.
 * Memory writes are an ingress event -- fail closed.
 */
export class OpenClawMemoryPhiError extends Error {
    agentId;
    key;
    constructor(agentId, key) {
        super(`OpenClaw agent "${agentId}" attempted to write PHI to memory key "${key}" ` +
            'under an active HIPAA pack binding. ' +
            'Memory write blocked at ingress gate. Fail closed.');
        this.name = 'OpenClawMemoryPhiError';
        this.agentId = agentId;
        this.key = key;
    }
}
/**
 * Thrown when an outbound message is destined for an unsanctioned messaging
 * platform workspace (workspace not in the sanctionedWorkspaces allowlist).
 */
export class OpenClawUnsanctionedWorkspaceError extends Error {
    agentId;
    platform;
    workspaceId;
    constructor(agentId, platform, workspaceId) {
        super(`OpenClaw agent "${agentId}" attempted to send to ${platform} workspace "${workspaceId}" ` +
            'which is not in the sanctioned workspace allowlist. ' +
            'Outbound message blocked at egress guard.');
        this.name = 'OpenClawUnsanctionedWorkspaceError';
        this.agentId = agentId;
        this.platform = platform;
        this.workspaceId = workspaceId;
    }
}
// ---------------------------------------------------------------------------
// Pseudonymisation helper
// ---------------------------------------------------------------------------
/**
 * Pseudonymise a string value using HMAC-SHA256 with the adapter's internal
 * key. Used for cross-session memory retrieval when regulated packs are bound.
 *
 * The returned token is deterministic for the same input + key (allowing
 * legitimate re-identification via a separately held reidentification token)
 * but opaque to external observers.
 */
function pseudonymise(value, key) {
    return crypto.createHmac('sha256', key).update(value).digest('hex');
}
// ---------------------------------------------------------------------------
// OpenClawAdapter
// ---------------------------------------------------------------------------
export class OpenClawAdapter extends BaseOrchestratorAdapter {
    adapterId = 'openclaw';
    orchestratorId = 'openclaw';
    openClawConfig;
    /** Active agent sessions: agentId -> contextSnapshot */
    activeSessions = new Map();
    /** Per-session action call counters */
    actionCallCounters = new Map();
    /** Per-session memory write counters */
    memoryWriteCounters = new Map();
    /** Per-session outbound message counters */
    outboundMessageCounters = new Map();
    /** Pseudonymisation key (session-scoped random if not provided in config) */
    pseudoKey;
    constructor(config) {
        super();
        this.openClawConfig = {
            blockPhiMemoryWrite: true,
            pseudonymiseCrossSessionReads: true,
            enforcePackBinding: true,
            ...config,
        };
        // Use provided key or generate a session-scoped random key
        this.pseudoKey = config.pseudonymisationKey ?? crypto.randomBytes(32).toString('hex');
    }
    // ---------------------------------------------------------------------------
    // agentStart -- gate at OpenClaw agent session start
    // ---------------------------------------------------------------------------
    /**
     * Called when an OpenClaw agent session starts.
     *   1. Agent registration check
     *   2. Pack-binding resolver
     *   3. Provider / model compliance check
     *   4. Platform authorisation check
     */
    async agentStart(agentId, userId, platform, contextSnapshot) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        // -- Circuit breaker -------------------------------------------------------
        if (this.isCircuitOpen()) {
            const reason = 'Circuit breaker is open; OpenClaw agent session denied';
            this.emitCanonical('orchestrator:task-denied', { agentId, userId, platform, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_CIRCUIT_OPEN',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Agent registration check ---------------------------------------------
        const isRegistered = this.openClawConfig.registeredAgents.length === 0
            ? false
            : this.openClawConfig.registeredAgents.some((id) => agentId === id || agentId.startsWith(id));
        if (!isRegistered) {
            const reason = `OpenClaw agent "${agentId}" is not registered under governance`;
            this.emitCanonical('orchestrator:task-denied', { agentId, userId, platform, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_UNREGISTERED_AGENT',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Pack-binding resolver -------------------------------------------------
        if (this.openClawConfig.enforcePackBinding !== false &&
            (!contextSnapshot.packBinding || contextSnapshot.packBinding.length === 0)) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                userId,
                platform,
                reason: 'Missing pack binding at OpenClaw agentStart',
            });
            this.recordFailure();
            throw new OpenClawUnboundDataError(agentId, 'agentStart');
        }
        // -- Model compliance check -----------------------------------------------
        if (!this.openClawConfig.approvedModels.includes(contextSnapshot.model)) {
            const reason = `OpenClaw model "${contextSnapshot.model}" is not on the approved model list ` +
                `for pack binding [${contextSnapshot.packBinding.join(', ')}].`;
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                model: contextSnapshot.model,
                approvedModels: this.openClawConfig.approvedModels,
                reason,
            });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_UNAPPROVED_MODEL',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Platform authorisation check -----------------------------------------
        if (!contextSnapshot.authorisedPlatforms.includes(platform)) {
            const reason = `OpenClaw agent "${agentId}" is not authorised for platform "${platform}". ` +
                `Authorised platforms: [${contextSnapshot.authorisedPlatforms.join(', ')}].`;
            this.emitCanonical('orchestrator:task-denied', { agentId, platform, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_UNAUTHORISED_PLATFORM',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Register session state -----------------------------------------------
        this.activeSessions.set(agentId, contextSnapshot);
        this.actionCallCounters.set(agentId, 0);
        this.memoryWriteCounters.set(agentId, 0);
        this.outboundMessageCounters.set(agentId, 0);
        // -- Emit canonical spawn event -------------------------------------------
        this.emitCanonical('orchestrator:workflow-start', {
            agentId,
            userId,
            platform,
            model: contextSnapshot.model,
            packBinding: contextSnapshot.packBinding,
            hasPersistentMemory: contextSnapshot.hasPersistentMemory,
            authorisedPlatforms: contextSnapshot.authorisedPlatforms,
            providerId: contextSnapshot.providerId ?? null,
            auditId,
        });
        this.recordSuccess();
        return {
            type: 'ALLOW',
            reason: `OpenClaw agent "${agentId}" approved on ${platform} (pack: ${contextSnapshot.packBinding.join(',')})`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // memoryWrite -- ingress gate for persistent memory writes (PT-ADAPT-OPENCLAW-1)
    // ---------------------------------------------------------------------------
    /**
     * Called before OpenClaw writes a value to its persistent memory store.
     * Every memory write is an ingress event that must be classified.
     *
     * Under HIPAA binding:
     *   - PHI in memory values is blocked (fail closed)
     *   - Memory write is audited with phi-detection flag
     *
     * Throws OpenClawMemoryPhiError when PHI is detected under HIPAA + blockPhiMemoryWrite=true.
     */
    async memoryWrite(agentId, key, value, sessionId) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const snapshot = this.activeSessions.get(agentId);
        if (!snapshot) {
            const reason = `OpenClaw agent "${agentId}" called memoryWrite without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { agentId, key, reason });
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const valueString = typeof value === 'string' ? value : JSON.stringify(value);
        const hasPhi = this.containsPhiPattern(valueString);
        const hipaaActive = this.hasHipaaBinding(snapshot.packBinding);
        // Track write count
        const writeCount = (this.memoryWriteCounters.get(agentId) ?? 0) + 1;
        this.memoryWriteCounters.set(agentId, writeCount);
        this.emitCanonical('orchestrator:plan-step-begin', {
            agentId,
            stage: 'memory-write',
            key,
            hasPhi,
            hipaaActive,
            sessionId: sessionId ?? null,
            writeCount,
            layer: 'openclaw-memory',
            auditId,
        });
        // Fail closed: PHI memory write blocked under HIPAA (PT-ADAPT-OPENCLAW-1)
        if (hasPhi && hipaaActive && this.openClawConfig.blockPhiMemoryWrite !== false) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                key,
                stage: 'memory-write',
                reason: `OpenClaw memory write for key "${key}" contains PHI under active HIPAA pack binding. ` +
                    'Memory write blocked at ingress gate. Fail closed.',
                layer: 'memory-phi-gate',
            });
            this.recordFailure();
            throw new OpenClawMemoryPhiError(agentId, key);
        }
        // Non-blocking PHI warning outside HIPAA
        if (hasPhi && !hipaaActive) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                key,
                stage: 'memory-write',
                reason: `OpenClaw memory write for key "${key}" contains PHI. ` +
                    'No HIPAA binding -- surfaced to compliance review.',
                layer: 'memory-phi-warn',
            });
        }
        return { auditId };
    }
    // ---------------------------------------------------------------------------
    // memoryRead -- cross-session memory read with pseudonymisation (PT-ADAPT-OPENCLAW-3)
    // ---------------------------------------------------------------------------
    /**
     * Called before OpenClaw reads from its persistent memory store.
     * Cross-session reads are a potential PHI/PII leak surface.
     *
     * When regulated packs are bound (GDPR, HIPAA, FCRA):
     *   - Protected identifiers in memory keys are pseudonymised before surfacing
     *     in audit events (PT-ADAPT-OPENCLAW-3).
     *
     * Returns the (potentially pseudonymised) key that was read.
     */
    async memoryRead(agentId, key, sessionId) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const snapshot = this.activeSessions.get(agentId);
        if (!snapshot) {
            const reason = `OpenClaw agent "${agentId}" called memoryRead without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { agentId, key, reason });
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const isCrossSession = sessionId !== undefined;
        const isRegulated = this.hasRegulatedBinding(snapshot.packBinding);
        // Pseudonymise the key in audit events for cross-session reads under regulated packs
        let pseudonymisedKey = null;
        if (isCrossSession &&
            isRegulated &&
            this.openClawConfig.pseudonymiseCrossSessionReads !== false) {
            pseudonymisedKey = pseudonymise(key, this.pseudoKey);
        }
        this.emitCanonical('orchestrator:plan-step-begin', {
            agentId,
            stage: 'memory-read',
            // Only surface the pseudonymised key in the audit event, never the raw key
            // when regulated + cross-session (PT-ADAPT-OPENCLAW-3)
            key: pseudonymisedKey ?? key,
            keyPseudonymised: pseudonymisedKey !== null,
            isCrossSession,
            isRegulated,
            layer: 'openclaw-memory',
            auditId,
        });
        return { auditId, pseudonymisedKey };
    }
    // ---------------------------------------------------------------------------
    // outboundMessage -- egress gate for messaging platform messages (PT-ADAPT-OPENCLAW-2)
    // ---------------------------------------------------------------------------
    /**
     * Called before OpenClaw sends a message to a messaging platform.
     * Every outbound message is an egress event -- the nine-guard stack applies.
     *
     *   1. Active-session check
     *   2. Platform in authorised platforms list
     *   3. Workspace in sanctioned workspace allowlist (PT-ADAPT-OPENCLAW-2)
     *   4. PHI scan on message content
     *
     * Throws OpenClawUnsanctionedWorkspaceError for unsanctioned workspaces.
     */
    async outboundMessage(agentId, platform, message) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const snapshot = this.activeSessions.get(agentId);
        if (!snapshot) {
            const reason = `OpenClaw agent "${agentId}" called outboundMessage without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { agentId, platform, reason });
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // -- Platform authorisation -----------------------------------------------
        if (!snapshot.authorisedPlatforms.includes(platform)) {
            const reason = `OpenClaw agent "${agentId}" is not authorised to send on platform "${platform}".`;
            this.emitCanonical('orchestrator:task-denied', { agentId, platform, reason });
            this.recordFailure();
            return { type: ActionDecisionType.DENY, reason, auditId };
        }
        // -- Workspace sanctioning (PT-ADAPT-OPENCLAW-2) --------------------------
        const workspaceId = message.workspaceId ?? message.recipientId;
        const sanctionedSet = this.openClawConfig.sanctionedWorkspaces.get(platform);
        if (sanctionedSet !== undefined) {
            // If the map has an entry for this platform, check the workspace
            if (!sanctionedSet.has(workspaceId)) {
                const reason = `OpenClaw agent "${agentId}" attempted to send to ${platform} workspace "${workspaceId}" ` +
                    'which is NOT in the sanctioned workspace allowlist. Outbound message blocked.';
                this.emitCanonical('orchestrator:task-denied', {
                    agentId,
                    platform,
                    workspaceId,
                    reason,
                    layer: 'workspace-allowlist-egress',
                });
                this.recordFailure();
                throw new OpenClawUnsanctionedWorkspaceError(agentId, platform, workspaceId);
            }
        }
        // If the platform is not in sanctionedWorkspaces at all, we allow (no policy configured)
        // -- PHI scan on outbound content -----------------------------------------
        const hasPhi = this.containsPhiPattern(message.content);
        const hipaaActive = this.hasHipaaBinding(snapshot.packBinding);
        if (hasPhi) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                platform,
                workspaceId,
                stage: 'outbound-message',
                reason: `OpenClaw outbound message to ${platform} contains PHI/PAN. ` +
                    `HIPAA active: ${hipaaActive}. Blocked at egress guard.`,
                layer: 'outbound-phi-egress',
            });
            this.recordFailure();
            return { type: ActionDecisionType.DENY, reason: `PHI detected in outbound message to ${platform}`, auditId };
        }
        // -- Track and approve ----------------------------------------------------
        const msgCount = (this.outboundMessageCounters.get(agentId) ?? 0) + 1;
        this.outboundMessageCounters.set(agentId, msgCount);
        this.emitCanonical('orchestrator:plan-step-begin', {
            agentId,
            platform,
            workspaceId,
            recipientId: message.recipientId,
            contentLength: message.content.length,
            attachmentCount: message.attachmentNames?.length ?? 0,
            outboundMessageCount: msgCount,
            layer: 'openclaw-egress',
        });
        return {
            type: ActionDecisionType.ALLOW,
            reason: `OpenClaw outbound message to ${platform}/${workspaceId} approved`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // actionCall -- pre-action gate
    // ---------------------------------------------------------------------------
    /**
     * Called before every OpenClaw action.
     *   1. Active-session check
     *   2. Pack-binding check
     *   3. Runaway counter
     *   4. Sensitive-action REQUIRE_APPROVAL routing
     *   5. Exfiltration scan on sensitive argument keys
     */
    async actionCall(agentId, actionName, args, platform) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (!this.activeSessions.has(agentId)) {
            const reason = `OpenClaw agent "${agentId}" called actionCall without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { agentId, actionName, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const snapshot = this.activeSessions.get(agentId);
        if (this.openClawConfig.enforcePackBinding !== false &&
            (!snapshot.packBinding || snapshot.packBinding.length === 0)) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                actionName,
                reason: 'Missing pack binding at OpenClaw actionCall',
            });
            this.recordFailure();
            throw new OpenClawUnboundDataError(agentId, `actionCall(${actionName})`);
        }
        // -- Runaway counter ------------------------------------------------------
        const callCount = (this.actionCallCounters.get(agentId) ?? 0) + 1;
        this.actionCallCounters.set(agentId, callCount);
        const maxCalls = this.openClawConfig.maxActionCallsPerSession;
        if (maxCalls !== undefined && callCount > maxCalls) {
            const reason = `OpenClaw agent "${agentId}" exceeded maxActionCallsPerSession (${maxCalls}). Runaway suppressed.`;
            this.emitCanonical('orchestrator:task-denied', { agentId, actionName, callCount, reason });
            this.recordFailure();
            return { type: ActionDecisionType.DENY, reason, auditId };
        }
        // -- Exfiltration scan on sensitive argument keys -------------------------
        for (const key of this.openClawConfig.sensitiveArgKeys) {
            const value = args[key];
            if (typeof value === 'string' && this.containsPhiPattern(value)) {
                const reason = `Possible data exfiltration in OpenClaw action args for "${actionName}" ` +
                    `(key: "${key}"). Blocked at G5/G7 egress guard.`;
                this.emitCanonical('orchestrator:task-denied', { agentId, actionName, exfilKey: key, reason });
                this.recordFailure();
                return { type: ActionDecisionType.DENY, reason, auditId };
            }
        }
        // -- Sensitive-action approval routing ------------------------------------
        if (this.openClawConfig.sensitiveActions.includes(actionName)) {
            const tier = 'human-reviewer';
            const queueId = this.config.reviewerQueues[tier];
            if (!queueId) {
                const reason = `OpenClaw action "${actionName}" is sensitive but no human-reviewer queue binding exists. Fail closed.`;
                this.emitCanonical('orchestrator:task-denied', { agentId, actionName, reason });
                this.recordFailure();
                return { type: ActionDecisionType.DENY, reason, auditId };
            }
            this.emitCanonical('orchestrator:reviewer-routed', {
                agentId,
                actionName,
                platform: platform ?? null,
                tier,
                queueId,
                layer: 'openclaw',
            });
            return {
                type: ActionDecisionType.REQUIRE_APPROVAL,
                reason: `OpenClaw action "${actionName}" requires human-reviewer approval`,
                auditId,
                metadata: { reviewerTier: tier, layer: 'openclaw', agentId, actionName },
            };
        }
        // -- Approve ---------------------------------------------------------------
        this.emitCanonical('orchestrator:task-approved', {
            agentId,
            actionName,
            platform: platform ?? null,
            callCount,
            layer: 'openclaw',
        });
        return {
            type: ActionDecisionType.ALLOW,
            reason: `OpenClaw action "${actionName}" approved for agent "${agentId}"`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // actionResult -- post-action audit + output classifier
    // ---------------------------------------------------------------------------
    async actionResult(agentId, actionName, 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', {
            agentId,
            actionName,
            success: result.success,
            durationMs: result.durationMs ?? null,
            suspiciousOutput: hasSuspiciousOutput,
            layer: 'openclaw',
        });
        if (hasSuspiciousOutput) {
            this.emitCanonical('orchestrator:task-denied', {
                agentId,
                actionName,
                reason: 'OpenClaw action result contains possible PHI/PAN. Surfaced to compliance review queue.',
                layer: 'post-action-classifier',
            });
        }
    }
    // ---------------------------------------------------------------------------
    // llmCall -- pre-LLM gate (multi-model compliance per-call)
    // ---------------------------------------------------------------------------
    async llmCall(agentId, model, messages) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (!this.activeSessions.has(agentId)) {
            const reason = `OpenClaw agent "${agentId}" called llmCall without agentStart`;
            this.emitCanonical('orchestrator:task-denied', { agentId, model, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_NO_ACTIVE_SESSION',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const snapshot = this.activeSessions.get(agentId);
        // Per-call provider compliance (multi-model)
        if (!this.openClawConfig.approvedModels.includes(model)) {
            const reason = `OpenClaw model "${model}" is not on the approved model list for ` +
                `pack binding [${snapshot.packBinding.join(', ')}].`;
            this.emitCanonical('orchestrator:task-denied', { agentId, model, reason });
            this.recordFailure();
            throw new OrchestratorError({
                message: reason,
                code: 'OPENCLAW_UNAPPROVED_MODEL',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        this.emitCanonical('orchestrator:plan-step-begin', {
            agentId,
            model,
            messageCount: messages.length,
            packBinding: snapshot.packBinding,
            layer: 'openclaw',
        });
        return {
            type: ActionDecisionType.ALLOW,
            reason: `OpenClaw LLM call to "${model}" approved for agent "${agentId}"`,
            auditId,
        };
    }
    // ---------------------------------------------------------------------------
    // agentEnd -- terminal audit event sealing
    // ---------------------------------------------------------------------------
    async agentEnd(agentId, outcome) {
        this.assertInitialized();
        const snapshot = this.activeSessions.get(agentId);
        const actionCallCount = this.actionCallCounters.get(agentId) ?? 0;
        const memoryWrites = this.memoryWriteCounters.get(agentId) ?? 0;
        const outboundMessages = this.outboundMessageCounters.get(agentId) ?? 0;
        this.emitCanonical('orchestrator:workflow-complete', {
            agentId,
            success: outcome.success,
            summary: outcome.summary ?? (outcome.success ? 'OpenClaw session complete' : 'OpenClaw session failed'),
            durationMs: outcome.durationMs ?? null,
            error: outcome.error ?? null,
            actionCallCount: outcome.totalActionCalls ?? actionCallCount,
            memoryWrites: outcome.totalMemoryWrites ?? memoryWrites,
            outboundMessages: outcome.totalOutboundMessages ?? outboundMessages,
            packBinding: snapshot?.packBinding ?? [],
            model: snapshot?.model ?? null,
        });
        this.activeSessions.delete(agentId);
        this.actionCallCounters.delete(agentId);
        this.memoryWriteCounters.delete(agentId);
        this.outboundMessageCounters.delete(agentId);
        if (outcome.success) {
            this.recordSuccess();
        }
        else {
            this.recordFailure();
        }
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    hasHipaaBinding(packBinding) {
        return packBinding.some((p) => p.toLowerCase().includes('hipaa') || p.toLowerCase().includes('hitech'));
    }
    hasRegulatedBinding(packBinding) {
        return packBinding.some((p) => {
            const lower = p.toLowerCase();
            return (lower.includes('hipaa') ||
                lower.includes('hitech') ||
                lower.includes('gdpr') ||
                lower.includes('fcra') ||
                lower.includes('ccpa'));
        });
    }
    containsPhiPattern(text) {
        // H9b: normalize Unicode separators before PHI regex match.
        // Attackers can use Unicode lookalike colons (U+FF1A FULLWIDTH COLON,
        // U+2236 RATIO, U+A789 MODIFIER LETTER COLON, U+02F8 MODIFIER LETTER
        // RAISED COLON, U+FE13 PRESENTATION FORM FOR VERTICAL COLON) to bypass
        // patterns that match ASCII colon after field names like ssn: or mrn:.
        // NFKC normalization converts fullwidth/compatibility characters to ASCII.
        const normalized = text
            .normalize('NFKC')
            // Additional explicit replacements for separators not covered by NFKC
            .replace(/[\u2236\uFE13\u02F8\uFF1A]/g, ':')
            .replace(/[\u2013\u2014\u2212\uFF0D]/g, '-') // em/en dashes, minus sign
            .replace(/[\u2423\u00A0\u2009\u200A\u3000]/g, ' '); // space variants
        if (/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/.test(normalized))
            return true;
        if (/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,4}\b/.test(normalized))
            return true;
        if (/\b(patient_id|mrn|dob|date_of_birth|diagnosis|ssn|npi)\s*[:=]/i.test(normalized))
            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: 'OpenClaw 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: ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:task-approved', {
            taskId: task.taskId,
            agentName: task.assignedAgent.name,
        });
        return { type: ActionDecisionType.ALLOW, reason: `Task "${task.taskId}" approved via OpenClawAdapter`, 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: ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:plan-approved', { planId: plan.planId });
        return { type: 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.openClawConfig.sensitiveActions.includes(p));
        if (restricted.length > 0) {
            const reason = `OpenClaw 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: ActionDecisionType.DENY, reason, restrictedPermissions: restricted, auditId };
        }
        this.emitCanonical('orchestrator:delegation-approved', {
            delegationId: d.delegationId,
            from: d.fromAgent.name,
            to: d.toAgent.name,
        });
        return { type: 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: 'OpenClaw role edge permitted', auditId };
    }
    translateError(raw) {
        if (raw instanceof OrchestratorError)
            return raw;
        const message = raw instanceof Error ? raw.message : String(raw);
        if (message.includes('OpenClawMemoryPhiError') || message.includes('memory write blocked')) {
            return new OrchestratorError({
                message: `OpenClaw memory PHI violation: ${message}`,
                code: 'OPENCLAW_MEMORY_PHI',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        if (message.includes('OpenClawUnsanctionedWorkspaceError') || message.includes('sanctioned workspace')) {
            return new OrchestratorError({
                message: `OpenClaw unsanctioned workspace: ${message}`,
                code: 'OPENCLAW_UNSANCTIONED_WORKSPACE',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        return new OrchestratorError({
            message,
            code: 'OPENCLAW_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,
        };
    }
}
//# sourceMappingURL=openclaw.js.map