/**
 * AgentVault OpenClaw Channel Plugin
 *
 * Registers AgentVault as a first-class OpenClaw messaging channel.
 * Messages from the AgentVault UI are decrypted and routed into the
 * OpenClaw agent loop; agent replies are encrypted and sent back.
 *
 * Installation (one-time):
 *   npx @agentvault/agentvault setup --token=av_tok_...
 *
 * After setup, restart OpenClaw — no further configuration needed.
 */
import { resolve } from "node:path";
import { SecureChannel } from "./channel.js";
// Runtime injected by register() — used in inbound handler
let _ocRuntime = null;
// Active channel instances keyed by accountId
const _channels = new Map();
export function setOcRuntime(runtime) {
    _ocRuntime = runtime;
}
export function getActiveChannel(accountId = "default") {
    return _channels.get(accountId);
}
// ---------------------------------------------------------------------------
// Channel plugin definition
// ---------------------------------------------------------------------------
export const agentVaultPlugin = {
    id: "agentvault",
    meta: {
        id: "agentvault",
        label: "AgentVault",
        selectionLabel: "AgentVault (E2E Encrypted)",
        docsPath: "https://agentvault.chat/docs",
        blurb: "Zero-knowledge, end-to-end encrypted messaging between owners and their AI agents.",
        aliases: ["av", "agent-vault"],
    },
    capabilities: {
        chatTypes: ["direct"],
    },
    config: {
        listAccountIds: (cfg) => {
            const av = cfg?.channels?.agentvault;
            return av?.dataDir ? ["default"] : [];
        },
        resolveAccount: (cfg, accountId) => {
            const av = cfg?.channels?.agentvault ?? {};
            return {
                accountId: accountId ?? "default",
                dataDir: av.dataDir ?? "~/.openclaw/agentvault",
                apiUrl: av.apiUrl ?? "https://api.agentvault.chat",
                agentName: av.agentName ?? "OpenClaw Agent",
                httpPort: av.httpPort ?? 18790,
                configured: Boolean(av.dataDir),
            };
        },
    },
    gateway: {
        startAccount: async (ctx) => {
            const { account, cfg, log, abortSignal } = ctx;
            if (!account.configured) {
                throw new Error("AgentVault channel is not configured.\n" +
                    "Run the one-time setup: npx @agentvault/agentvault setup --token=av_tok_...\n" +
                    "Then restart OpenClaw: openclaw gateway restart");
            }
            const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? "~"));
            log?.(`[AgentVault] starting channel (dataDir=${dataDir})`);
            const channel = new SecureChannel({
                // No invite token needed — resuming from persisted enrolled state
                inviteToken: "",
                dataDir,
                apiUrl: account.apiUrl,
                agentName: account.agentName,
                onMessage: async (plaintext, metadata) => {
                    if (!_ocRuntime) {
                        log?.("[AgentVault] runtime not ready — dropping inbound message");
                        return;
                    }
                    try {
                        await _handleInbound({ plaintext, metadata, channel, account, cfg });
                    }
                    catch (err) {
                        log?.(`[AgentVault] inbound dispatch error: ${String(err)}`);
                    }
                },
                onStateChange: (state) => {
                    log?.(`[AgentVault] state → ${state}`);
                },
            });
            _channels.set(account.accountId, channel);
            // Start local HTTP server for proactive sends when channel is ready
            const httpPort = account.httpPort;
            channel.on("ready", () => {
                channel.startHttpServer(httpPort);
                log?.(`[AgentVault] HTTP send server listening on http://127.0.0.1:${httpPort}`);
            });
            // Respect gateway abort signal (OpenClaw gateway shutdown)
            abortSignal?.addEventListener("abort", () => {
                _channels.delete(account.accountId);
            });
            await channel.start();
            return {
                stop: async () => {
                    await channel.stop();
                    _channels.delete(account.accountId);
                },
            };
        },
    },
    outbound: {
        deliveryMode: "direct",
        targets: [
            {
                id: "owner",
                label: "AgentVault Owner",
                accountId: "default",
            },
            {
                id: "default",
                label: "AgentVault Owner (default)",
                accountId: "default",
            },
        ],
        sendText: async ({ text, accountId, targetId, }) => {
            const resolvedId = accountId ?? (targetId === "owner" ? "default" : (targetId ?? "default"));
            const channel = _channels.get(resolvedId);
            if (!channel) {
                return { ok: false, error: "AgentVault channel is not connected" };
            }
            try {
                await channel.send(text);
                return { ok: true };
            }
            catch (err) {
                return { ok: false, error: String(err) };
            }
        },
    },
};
// ---------------------------------------------------------------------------
// Inbound message handler — routes decrypted messages into the agent loop
// ---------------------------------------------------------------------------
async function _handleInbound(params) {
    const { plaintext, metadata, channel, account, cfg } = params;
    const core = _ocRuntime;
    // Stable session key scoped to this AgentVault conversation
    const sessionKey = `agentvault:${account.accountId}:${(metadata.conversationId ?? "default").slice(0, 8)}`;
    const senderId = `agentvault:owner`;
    // Resolve agent routing (respects agents.list routing rules if configured)
    const route = core.channel.routing.resolveAgentRoute({
        cfg,
        channel: "agentvault",
        accountId: account.accountId,
        peer: { kind: "direct", id: senderId },
    });
    const storePath = core.channel.session.resolveStorePath(cfg?.session?.store, {
        agentId: route.agentId,
    });
    const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
    const previousTimestamp = core.channel.session.readSessionUpdatedAt({
        storePath,
        sessionKey: route.sessionKey,
    });
    // Format the message body with standard OpenClaw envelope (channel label, timestamps, etc.)
    const body = core.channel.reply.formatAgentEnvelope({
        channel: "AgentVault",
        from: "Owner",
        timestamp: new Date(metadata.timestamp).getTime(),
        previousTimestamp,
        envelope: envelopeOptions,
        body: plaintext,
    });
    // Build the inbound context payload OpenClaw expects
    const ctxPayload = core.channel.reply.finalizeInboundContext({
        Body: body,
        RawBody: plaintext,
        CommandBody: plaintext,
        From: senderId,
        To: `agentvault:agent:${account.accountId}`,
        SessionKey: route.sessionKey,
        AccountId: account.accountId,
        ChatType: "direct",
        ConversationLabel: "AgentVault",
        SenderName: "Owner",
        SenderId: senderId,
        Provider: "agentvault",
        Surface: "agentvault",
        MessageSid: metadata.messageId,
        Timestamp: new Date(metadata.timestamp).getTime(),
        OriginatingChannel: "agentvault",
        OriginatingTo: `agentvault:agent:${account.accountId}`,
        // Owner is cryptographically verified via X3DH + Double Ratchet enrollment
        CommandAuthorized: true,
    });
    // Record session metadata for context continuity
    await core.channel.session.recordInboundSession({
        storePath,
        sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
        ctx: ctxPayload,
        onRecordError: (err) => {
            core.error?.(`[AgentVault] session record failed: ${String(err)}`);
        },
    });
    // Dispatch to the OpenClaw agent loop; deliver replies back through the channel
    await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
        ctx: ctxPayload,
        cfg,
        dispatcherOptions: {
            deliver: async (payload) => {
                const text = (payload.text ?? "").trim();
                if (text) {
                    await channel.send(text);
                }
            },
            onError: (err, info) => {
                core.error?.(`[AgentVault] ${info?.kind ?? "reply"} error: ${String(err)}`);
            },
        },
        replyOptions: {},
    });
}
//# sourceMappingURL=openclaw-plugin.js.map