import os from 'node:os';
import path from 'node:path';
export const PIXCODE_PERMISSION_POLICY_PROTOCOL = 'pixcode.permission-policy.v1';
export const PERMISSION_CAPABILITIES = [
    'shell',
    'file_write',
    'external_directory',
    'network',
    'secret',
];
export const PERMISSION_POLICY_MODES = ['allow', 'deny', 'prompt', 'audit'];
const DEFAULT_MODES = {
    shell: 'audit',
    file_write: 'audit',
    external_directory: 'audit',
    network: 'audit',
    secret: 'audit',
};
const NETWORK_COMMAND_PATTERN = /\b(curl|wget|fetch|ssh|scp|rsync|gh|git\s+(?:clone|fetch|pull|push)|npm\s+(?:install|publish|view|pack|audit)|pnpm\s+(?:install|publish)|yarn\s+(?:install|publish)|pip\s+install|cargo\s+(?:install|publish)|go\s+(?:get|install)|docker\s+(?:pull|push|run))\b|https?:\/\//iu;
const FILE_WRITE_COMMAND_PATTERN = /(^|\s)(apply_patch|touch|mkdir|rm|mv|cp|tee|sed\s+-i|perl\s+-pi|truncate|chmod|chown)\b|>>?|\b(write|edit|patch|delete|create)\s+(?:file|folder|directory)\b/iu;
const SECRET_COMMAND_PATTERN = /\b(printenv|env|set)\b|\b(cat|type|less|more)\s+(\S*\.env\b|\S*secret\S*|\S*token\S*|\S*key\S*)/iu;
const SECRET_VALUE_PATTERN = /\b(?:sk|ghp|github_pat|glpat|npm)_[A-Za-z0-9_=-]{12,}\b|(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[A-Za-z0-9_./+=-]{8,}/iu;
function readRecord(value) {
    return value && typeof value === 'object' ? value : undefined;
}
function readString(value) {
    return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}
function readMode(value) {
    return typeof value === 'string' && PERMISSION_POLICY_MODES.includes(value)
        ? value
        : undefined;
}
function uniqueCapabilities(values) {
    return [...new Set(values.filter((value) => Boolean(value)))];
}
function requestId(value) {
    return value?.trim() || `perm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
}
function normalizePath(value) {
    if (!value?.trim())
        return undefined;
    try {
        return path.resolve(value);
    }
    catch {
        return undefined;
    }
}
function isInsidePath(basePath, candidatePath) {
    const base = normalizePath(basePath);
    const candidate = normalizePath(candidatePath);
    if (!base || !candidate)
        return true;
    if (candidate === base)
        return true;
    const relative = path.relative(base, candidate);
    return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative));
}
function collectInputPaths(input) {
    const paths = [];
    const visit = (value) => {
        if (typeof value === 'string') {
            if (/^(?:[A-Za-z]:[\\/]|\/|~\/|\.\.?[\\/])/u.test(value.trim())) {
                paths.push(value.trim().replace(/^~(?=$|[\\/])/u, os.homedir()));
            }
            return;
        }
        if (Array.isArray(value)) {
            value.forEach(visit);
            return;
        }
        const record = readRecord(value);
        if (!record)
            return;
        for (const [key, nested] of Object.entries(record)) {
            if (/path|file|dir|cwd|target|source|destination/iu.test(key)) {
                visit(nested);
            }
        }
    };
    visit(input);
    return paths;
}
function inferCapabilities(request) {
    const capabilities = [];
    if (request.capability)
        capabilities.push(request.capability);
    if (Array.isArray(request.capabilities))
        capabilities.push(...request.capabilities);
    const toolName = request.toolName?.toLocaleLowerCase('en') ?? '';
    const command = [
        request.command,
        typeof request.input === 'string' ? request.input : undefined,
        readString(readRecord(request.input)?.command),
        readString(readRecord(request.input)?.query),
    ].filter(Boolean).join('\n');
    const inputText = [
        command,
        typeof request.summary === 'string' ? request.summary : undefined,
        typeof request.input === 'object' ? JSON.stringify(request.input) : undefined,
    ].filter(Boolean).join('\n');
    if (toolName === 'bash' || toolName.includes('shell') || toolName.includes('terminal') || command.trim()) {
        capabilities.push('shell');
    }
    if (/write|edit|multiedit|notebookedit|delete|patch/iu.test(toolName) || FILE_WRITE_COMMAND_PATTERN.test(command)) {
        capabilities.push('file_write');
    }
    if (/webfetch|websearch|fetch|search/iu.test(toolName) || NETWORK_COMMAND_PATTERN.test(command)) {
        capabilities.push('network');
    }
    if (/secret|credential|token|keychain|vault/iu.test(toolName) || SECRET_COMMAND_PATTERN.test(command) || SECRET_VALUE_PATTERN.test(inputText)) {
        capabilities.push('secret');
    }
    const targetPaths = [
        ...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
        ...collectInputPaths(request.input),
    ];
    if (targetPaths.some((targetPath) => !isInsidePath(request.workspacePath ?? request.cwd, targetPath))) {
        capabilities.push('external_directory');
    }
    return uniqueCapabilities(capabilities);
}
export const DEFAULT_PERMISSION_POLICY = {
    protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
    modes: DEFAULT_MODES,
    allowedExternalDirectories: [],
    audit: true,
};
export function normalizePermissionPolicy(value) {
    const record = readRecord(value);
    const modeRecord = readRecord(record?.modes) ?? readRecord(record?.rules) ?? record ?? {};
    const modes = { ...DEFAULT_MODES };
    for (const capability of PERMISSION_CAPABILITIES) {
        modes[capability] = readMode(modeRecord[capability]) ?? modes[capability];
    }
    const allowedExternalDirectories = Array.isArray(record?.allowedExternalDirectories)
        ? record.allowedExternalDirectories
            .map((item) => readString(item))
            .filter((item) => Boolean(item))
        : [];
    return {
        protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
        modes,
        allowedExternalDirectories,
        audit: typeof record?.audit === 'boolean' ? record.audit : true,
    };
}
export function resolvePermissionPolicyFromMetadata(metadata) {
    const settings = readRecord(metadata?.settings);
    return normalizePermissionPolicy(metadata?.permissionPolicy ?? settings?.permissionPolicy);
}
export function redactPermissionText(value, context) {
    if (!value?.trim())
        return undefined;
    const candidates = [
        os.homedir(),
        context?.workspacePath,
        context?.cwd,
        context?.projectPath,
    ].filter((item) => Boolean(item && item.length > 2));
    let text = value;
    for (const candidate of candidates) {
        text = text.split(candidate).join('[workspace]');
    }
    return text
        .replace(SECRET_VALUE_PATTERN, '[redacted-secret]')
        .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gu, '[redacted-email]')
        .trim();
}
function modeAllowsExternalDirectory(request, policy) {
    const targetPaths = [
        ...(Array.isArray(request.targetPaths) ? request.targetPaths : []),
        ...collectInputPaths(request.input),
    ];
    if (!targetPaths.length || !policy.allowedExternalDirectories.length)
        return false;
    return targetPaths.every((targetPath) => policy.allowedExternalDirectories.some((allowedPath) => isInsidePath(allowedPath, targetPath)));
}
export function createPermissionApprovalRequest(decision, context) {
    return {
        id: decision.requestId,
        protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
        status: 'pending',
        capabilities: decision.capabilities,
        source: decision.event.source,
        toolName: decision.event.toolName,
        runId: context?.runId,
        nodeId: context?.nodeId,
        workflowId: context?.workflowId,
        adapterId: context?.adapterId,
        agentLabel: context?.agentLabel,
        summary: decision.event.summary,
        message: decision.message,
        createdAt: decision.event.createdAt,
    };
}
export function evaluatePermissionRequest({ policy: policyInput, request, context, }) {
    const policy = normalizePermissionPolicy(policyInput);
    const id = requestId(request.requestId);
    const capabilities = inferCapabilities(request);
    const modeByCapability = {};
    for (const capability of capabilities) {
        const mode = capability === 'external_directory' && modeAllowsExternalDirectory(request, policy)
            ? 'allow'
            : policy.modes[capability];
        modeByCapability[capability] = mode;
    }
    const modes = Object.values(modeByCapability);
    const behavior = modes.includes('deny')
        ? 'deny'
        : modes.includes('prompt')
            ? 'prompt'
            : 'allow';
    const status = behavior === 'deny'
        ? 'denied'
        : behavior === 'prompt'
            ? 'needs_approval'
            : 'allowed';
    const summary = redactPermissionText(request.summary
        ?? request.command
        ?? (request.toolName ? `${request.toolName} tool request` : `${request.source} permission request`), {
        workspacePath: request.workspacePath,
        cwd: request.cwd,
    });
    const capabilityText = capabilities.length ? capabilities.join(', ') : 'unclassified';
    const message = behavior === 'deny'
        ? `Permission policy denied ${capabilityText}.`
        : behavior === 'prompt'
            ? `Permission policy requires approval for ${capabilityText}.`
            : `Permission policy allowed ${capabilityText}.`;
    const event = {
        id,
        protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
        status,
        behavior,
        capabilities,
        source: request.source,
        toolName: request.toolName,
        summary,
        message,
        modeByCapability,
        audit: policy.audit || modes.includes('audit'),
        runId: context?.runId,
        nodeId: context?.nodeId,
        workflowId: context?.workflowId,
        adapterId: context?.adapterId,
        agentLabel: context?.agentLabel,
        createdAt: Date.now(),
    };
    const decisionBase = {
        protocol: PIXCODE_PERMISSION_POLICY_PROTOCOL,
        requestId: id,
        status,
        behavior,
        capabilities,
        modeByCapability,
        message,
        audit: event.audit,
        event,
    };
    return {
        ...decisionBase,
        approvalRequest: behavior === 'prompt'
            ? createPermissionApprovalRequest(decisionBase, context)
            : undefined,
    };
}
//# sourceMappingURL=permission-policy.js.map